lens-content-processor 0.3.0 → 0.4.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.d.ts +7 -2
- package/dist/bundler/article.js +19 -8
- package/dist/bundler/article.js.map +1 -1
- package/dist/content-schema.js +3 -2
- package/dist/content-schema.js.map +1 -1
- package/dist/flattener/index.d.ts +20 -1
- package/dist/flattener/index.js +476 -72
- package/dist/flattener/index.js.map +1 -1
- package/dist/flattener/test-helpers.d.ts +23 -0
- package/dist/flattener/test-helpers.js +58 -0
- package/dist/flattener/test-helpers.js.map +1 -0
- package/dist/index.d.ts +18 -2
- package/dist/index.js +53 -23
- package/dist/index.js.map +1 -1
- package/dist/parser/article.d.ts +1 -1
- package/dist/parser/article.js +8 -1
- package/dist/parser/article.js.map +1 -1
- package/dist/parser/course.js +11 -8
- package/dist/parser/course.js.map +1 -1
- package/dist/parser/learning-outcome.d.ts +7 -0
- package/dist/parser/learning-outcome.js +129 -86
- package/dist/parser/learning-outcome.js.map +1 -1
- package/dist/parser/lens.d.ts +26 -1
- package/dist/parser/lens.js +99 -10
- package/dist/parser/lens.js.map +1 -1
- package/dist/parser/module.d.ts +1 -1
- package/dist/parser/module.js +13 -8
- package/dist/parser/module.js.map +1 -1
- package/dist/parser/sections.d.ts +2 -0
- package/dist/parser/sections.js +53 -19
- package/dist/parser/sections.js.map +1 -1
- package/dist/parser/video-transcript.js +3 -0
- package/dist/parser/video-transcript.js.map +1 -1
- package/dist/validator/directives.d.ts +12 -0
- package/dist/validator/directives.js +428 -0
- package/dist/validator/directives.js.map +1 -0
- package/dist/validator/field-values.js +14 -0
- package/dist/validator/field-values.js.map +1 -1
- package/dist/validator/url-reachability.js +3 -1
- package/dist/validator/url-reachability.js.map +1 -1
- package/dist/validator/uuid.js +9 -6
- package/dist/validator/uuid.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"course.js","sourceRoot":"","sources":["../../src/parser/course.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAsB,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;
|
|
1
|
+
{"version":3,"file":"course.js","sourceRoot":"","sources":["../../src/parser/course.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,11 +10,18 @@ export interface ParsedTestRef {
|
|
|
10
10
|
resolvedPath?: string;
|
|
11
11
|
segments: ParsedLensSegment[];
|
|
12
12
|
}
|
|
13
|
+
export interface ParsedSubmoduleGroup {
|
|
14
|
+
title: string;
|
|
15
|
+
customSlug?: string;
|
|
16
|
+
lenses: ParsedLensRef[];
|
|
17
|
+
test?: ParsedTestRef;
|
|
18
|
+
}
|
|
13
19
|
export interface ParsedLearningOutcome {
|
|
14
20
|
id: string;
|
|
15
21
|
lenses: ParsedLensRef[];
|
|
16
22
|
test?: ParsedTestRef;
|
|
17
23
|
discussion?: string;
|
|
24
|
+
submodules?: ParsedSubmoduleGroup[];
|
|
18
25
|
}
|
|
19
26
|
export interface LearningOutcomeParseResult {
|
|
20
27
|
learningOutcome: ParsedLearningOutcome | null;
|
|
@@ -3,11 +3,11 @@ import { parseSections, LO_SECTION_TYPES } from './sections.js';
|
|
|
3
3
|
import { parseWikilink, resolveWikilinkPath, hasRelativePath } from './wikilink.js';
|
|
4
4
|
import { detectFieldTypos } from '../validator/field-typos.js';
|
|
5
5
|
import { validateFrontmatter } from '../validator/validate-frontmatter.js';
|
|
6
|
-
import { parseSegments, convertSegment,
|
|
6
|
+
import { parseSegments, convertSegment, stripAuthoringMarkup } from './lens.js';
|
|
7
7
|
export function parseLearningOutcome(content, file) {
|
|
8
8
|
const errors = [];
|
|
9
|
-
// Strip Obsidian comments before parsing
|
|
10
|
-
content =
|
|
9
|
+
// Strip authoring markup (CriticMarkup + Obsidian comments) before parsing
|
|
10
|
+
content = stripAuthoringMarkup(content);
|
|
11
11
|
// Step 1: Parse frontmatter and validate id field
|
|
12
12
|
const frontmatterResult = parseFrontmatter(content, file);
|
|
13
13
|
if (frontmatterResult.error) {
|
|
@@ -31,8 +31,9 @@ export function parseLearningOutcome(content, file) {
|
|
|
31
31
|
});
|
|
32
32
|
return { learningOutcome: null, errors };
|
|
33
33
|
}
|
|
34
|
-
// Step 2: Parse sections with H2 level and LO_SECTION_TYPES
|
|
35
|
-
const
|
|
34
|
+
// Step 2: Parse sections with H2 level and LO_SECTION_TYPES + 'submodule'
|
|
35
|
+
const loSectionTypes = new Set([...LO_SECTION_TYPES, 'submodule']);
|
|
36
|
+
const sectionsResult = parseSections(body, 2, loSectionTypes, file);
|
|
36
37
|
// Adjust line numbers to account for frontmatter
|
|
37
38
|
for (const error of sectionsResult.errors) {
|
|
38
39
|
if (error.line) {
|
|
@@ -43,31 +44,61 @@ export function parseLearningOutcome(content, file) {
|
|
|
43
44
|
for (const section of sectionsResult.sections) {
|
|
44
45
|
section.line += bodyStartLine - 1;
|
|
45
46
|
}
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
let testRef;
|
|
49
|
-
for (const section of sectionsResult.sections) {
|
|
50
|
-
// Detect likely typos in field names
|
|
47
|
+
// Helper: extract a lens ref from a parsed section
|
|
48
|
+
function extractLensRef(section) {
|
|
51
49
|
const typoWarnings = detectFieldTypos(section.fields, file, section.line);
|
|
52
50
|
errors.push(...typoWarnings);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
51
|
+
const source = section.fields.source;
|
|
52
|
+
if (!source) {
|
|
53
|
+
errors.push({
|
|
54
|
+
file,
|
|
55
|
+
line: section.line,
|
|
56
|
+
message: 'Lens section missing source:: field',
|
|
57
|
+
suggestion: "Add 'source:: [[../Lenses/filename.md|Display]]' to the lens section",
|
|
58
|
+
severity: 'error',
|
|
59
|
+
});
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const wikilink = parseWikilink(source);
|
|
63
|
+
if (!wikilink || wikilink.error) {
|
|
64
|
+
const suggestion = wikilink?.correctedPath
|
|
65
|
+
? `Did you mean '[[${wikilink.correctedPath}]]'?`
|
|
66
|
+
: 'Use format [[../Lenses/filename.md|Display Text]]';
|
|
67
|
+
errors.push({
|
|
68
|
+
file,
|
|
69
|
+
line: section.line,
|
|
70
|
+
message: wikilink?.error
|
|
71
|
+
? `${wikilink.error} in source:: field: ${source}`
|
|
72
|
+
: `Invalid wikilink format in source:: field: ${source}`,
|
|
73
|
+
suggestion,
|
|
74
|
+
severity: 'error',
|
|
75
|
+
});
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
if (!hasRelativePath(wikilink.path)) {
|
|
79
|
+
errors.push({
|
|
80
|
+
file,
|
|
81
|
+
line: section.line,
|
|
82
|
+
message: `source:: path must be relative (contain /): ${wikilink.path}`,
|
|
83
|
+
suggestion: 'Use format [[../Lenses/filename.md|Display Text]] with relative path',
|
|
84
|
+
severity: 'error',
|
|
85
|
+
});
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const resolvedPath = resolveWikilinkPath(wikilink.path, file);
|
|
89
|
+
const optional = section.fields.optional?.toLowerCase() === 'true';
|
|
90
|
+
return { source, resolvedPath, optional };
|
|
91
|
+
}
|
|
92
|
+
// Helper: extract test ref from a parsed section
|
|
93
|
+
function extractTestRef(section) {
|
|
94
|
+
const source = section.fields.source;
|
|
95
|
+
let resolvedPath;
|
|
96
|
+
if (source) {
|
|
66
97
|
const wikilink = parseWikilink(source);
|
|
67
98
|
if (!wikilink || wikilink.error) {
|
|
68
99
|
const suggestion = wikilink?.correctedPath
|
|
69
100
|
? `Did you mean '[[${wikilink.correctedPath}]]'?`
|
|
70
|
-
: 'Use format [[../
|
|
101
|
+
: 'Use format [[../Tests/filename.md|Display Text]]';
|
|
71
102
|
errors.push({
|
|
72
103
|
file,
|
|
73
104
|
line: section.line,
|
|
@@ -77,78 +108,89 @@ export function parseLearningOutcome(content, file) {
|
|
|
77
108
|
suggestion,
|
|
78
109
|
severity: 'error',
|
|
79
110
|
});
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
// Require relative path (must contain /)
|
|
83
|
-
if (!hasRelativePath(wikilink.path)) {
|
|
84
|
-
errors.push({
|
|
85
|
-
file,
|
|
86
|
-
line: section.line,
|
|
87
|
-
message: `source:: path must be relative (contain /): ${wikilink.path}`,
|
|
88
|
-
suggestion: 'Use format [[../Lenses/filename.md|Display Text]] with relative path',
|
|
89
|
-
severity: 'error',
|
|
90
|
-
});
|
|
91
|
-
continue;
|
|
111
|
+
return undefined;
|
|
92
112
|
}
|
|
93
|
-
|
|
94
|
-
const optional = section.fields.optional?.toLowerCase() === 'true';
|
|
95
|
-
lenses.push({
|
|
96
|
-
source,
|
|
97
|
-
resolvedPath,
|
|
98
|
-
optional,
|
|
99
|
-
});
|
|
113
|
+
resolvedPath = resolveWikilinkPath(wikilink.path, file);
|
|
100
114
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// Parse wikilink and resolve path
|
|
106
|
-
const wikilink = parseWikilink(source);
|
|
107
|
-
if (!wikilink || wikilink.error) {
|
|
108
|
-
const suggestion = wikilink?.correctedPath
|
|
109
|
-
? `Did you mean '[[${wikilink.correctedPath}]]'?`
|
|
110
|
-
: 'Use format [[../Tests/filename.md|Display Text]]';
|
|
111
|
-
errors.push({
|
|
112
|
-
file,
|
|
113
|
-
line: section.line,
|
|
114
|
-
message: wikilink?.error
|
|
115
|
-
? `${wikilink.error} in source:: field: ${source}`
|
|
116
|
-
: `Invalid wikilink format in source:: field: ${source}`,
|
|
117
|
-
suggestion,
|
|
118
|
-
severity: 'error',
|
|
119
|
-
});
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
resolvedPath = resolveWikilinkPath(wikilink.path, file);
|
|
115
|
+
const { segments: rawSegments, errors: segmentErrors } = parseSegments(section.body, section.line + 1, file);
|
|
116
|
+
for (const err of segmentErrors) {
|
|
117
|
+
if (err.line) {
|
|
118
|
+
err.line += bodyStartLine - 1;
|
|
123
119
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
120
|
+
}
|
|
121
|
+
errors.push(...segmentErrors);
|
|
122
|
+
const testSegments = [];
|
|
123
|
+
for (const rawSeg of rawSegments) {
|
|
124
|
+
const { segment, errors: conversionErrors } = convertSegment(rawSeg, 'page', file);
|
|
125
|
+
for (const err of conversionErrors) {
|
|
128
126
|
if (err.line) {
|
|
129
127
|
err.line += bodyStartLine - 1;
|
|
130
128
|
}
|
|
131
129
|
}
|
|
132
|
-
errors.push(...
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
130
|
+
errors.push(...conversionErrors);
|
|
131
|
+
if (segment) {
|
|
132
|
+
testSegments.push(segment);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { source, resolvedPath, segments: testSegments };
|
|
136
|
+
}
|
|
137
|
+
// Step 3: Check for submodule sections
|
|
138
|
+
const hasSubmodules = sectionsResult.sections.some(s => s.type === 'submodule');
|
|
139
|
+
const lenses = [];
|
|
140
|
+
let testRef;
|
|
141
|
+
let submodules;
|
|
142
|
+
if (hasSubmodules) {
|
|
143
|
+
// All-or-nothing: reject top-level lens/test sections when submodules exist
|
|
144
|
+
const orphanedSections = sectionsResult.sections.filter(s => s.type !== 'submodule');
|
|
145
|
+
if (orphanedSections.length > 0) {
|
|
146
|
+
errors.push({
|
|
147
|
+
file,
|
|
148
|
+
line: orphanedSections[0].line,
|
|
149
|
+
message: 'Content found outside submodule boundaries — when using Submodule markers, all content must be inside a submodule',
|
|
150
|
+
suggestion: 'Move this content into a Submodule section',
|
|
151
|
+
severity: 'error',
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
submodules = [];
|
|
155
|
+
for (const section of sectionsResult.sections) {
|
|
156
|
+
if (section.type !== 'submodule')
|
|
157
|
+
continue;
|
|
158
|
+
const group = {
|
|
159
|
+
title: section.title,
|
|
160
|
+
customSlug: section.fields.slug,
|
|
161
|
+
lenses: [],
|
|
162
|
+
};
|
|
163
|
+
// Process children (lens/test sections at H3)
|
|
164
|
+
if (section.children) {
|
|
165
|
+
for (const child of section.children) {
|
|
166
|
+
if (child.type === 'lens') {
|
|
167
|
+
const ref = extractLensRef(child);
|
|
168
|
+
if (ref) {
|
|
169
|
+
group.lenses.push(ref);
|
|
170
|
+
lenses.push(ref); // Also add to flat list for validation
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else if (child.type === 'test') {
|
|
174
|
+
group.test = extractTestRef(child);
|
|
140
175
|
}
|
|
141
|
-
}
|
|
142
|
-
errors.push(...conversionErrors);
|
|
143
|
-
if (segment) {
|
|
144
|
-
testSegments.push(segment);
|
|
145
176
|
}
|
|
146
177
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
178
|
+
submodules.push(group);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
// Normal path: extract lens refs from top-level sections
|
|
183
|
+
for (const section of sectionsResult.sections) {
|
|
184
|
+
const typoWarnings = detectFieldTypos(section.fields, file, section.line);
|
|
185
|
+
errors.push(...typoWarnings);
|
|
186
|
+
if (section.type === 'lens') {
|
|
187
|
+
const ref = extractLensRef(section);
|
|
188
|
+
if (ref)
|
|
189
|
+
lenses.push(ref);
|
|
190
|
+
}
|
|
191
|
+
else if (section.type === 'test') {
|
|
192
|
+
testRef = extractTestRef(section);
|
|
193
|
+
}
|
|
152
194
|
}
|
|
153
195
|
}
|
|
154
196
|
// Step 4: Validate at least one lens exists
|
|
@@ -167,6 +209,7 @@ export function parseLearningOutcome(content, file) {
|
|
|
167
209
|
lenses,
|
|
168
210
|
test: testRef,
|
|
169
211
|
discussion: frontmatter.discussion,
|
|
212
|
+
submodules,
|
|
170
213
|
};
|
|
171
214
|
return { learningOutcome, errors };
|
|
172
215
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"learning-outcome.js","sourceRoot":"","sources":["../../src/parser/learning-outcome.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"learning-outcome.js","sourceRoot":"","sources":["../../src/parser/learning-outcome.ts"],"names":[],"mappings":"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"}
|
package/dist/parser/lens.d.ts
CHANGED
|
@@ -34,7 +34,17 @@ export interface ParsedQuestionSegment {
|
|
|
34
34
|
optional?: boolean;
|
|
35
35
|
feedback?: boolean;
|
|
36
36
|
}
|
|
37
|
-
export
|
|
37
|
+
export interface ParsedRoleplaySegment {
|
|
38
|
+
type: 'roleplay';
|
|
39
|
+
id: string;
|
|
40
|
+
content: string;
|
|
41
|
+
aiInstructions: string;
|
|
42
|
+
openingMessage?: string;
|
|
43
|
+
assessmentInstructions?: string;
|
|
44
|
+
optional?: boolean;
|
|
45
|
+
feedback?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export type ParsedLensSegment = ParsedTextSegment | ParsedChatSegment | ParsedArticleExcerptSegment | ParsedVideoExcerptSegment | ParsedQuestionSegment | ParsedRoleplaySegment;
|
|
38
48
|
export interface ParsedLensSection {
|
|
39
49
|
type: string;
|
|
40
50
|
title: string;
|
|
@@ -45,6 +55,7 @@ export interface ParsedLensSection {
|
|
|
45
55
|
}
|
|
46
56
|
export interface ParsedLens {
|
|
47
57
|
id: string;
|
|
58
|
+
tldr?: string;
|
|
48
59
|
sections: ParsedLensSection[];
|
|
49
60
|
}
|
|
50
61
|
export interface LensParseResult {
|
|
@@ -77,6 +88,20 @@ export declare function convertSegment(raw: RawSegment, sectionType: string, fil
|
|
|
77
88
|
* Handles both inline (%% ... %% on same line) and block (multiline) comments.
|
|
78
89
|
*/
|
|
79
90
|
export declare function stripObsidianComments(content: string): string;
|
|
91
|
+
/**
|
|
92
|
+
* Strip CriticMarkup from content using reject-all-changes behavior:
|
|
93
|
+
* - {>>comments<<} → removed
|
|
94
|
+
* - {++additions++} → removed
|
|
95
|
+
* - {--deletions--} → inner content kept (original preserved)
|
|
96
|
+
* - {~~old~>new~~} → old text kept
|
|
97
|
+
* - {==highlights==} → inner content kept, markers removed
|
|
98
|
+
*/
|
|
99
|
+
export declare function stripCriticMarkup(content: string): string;
|
|
100
|
+
/**
|
|
101
|
+
* Strip all authoring markup (CriticMarkup + Obsidian comments) from content.
|
|
102
|
+
* Call at the top of each parser before any processing.
|
|
103
|
+
*/
|
|
104
|
+
export declare function stripAuthoringMarkup(content: string): string;
|
|
80
105
|
/**
|
|
81
106
|
* Parse a lens file into structured lens data.
|
|
82
107
|
*
|
package/dist/parser/lens.js
CHANGED
|
@@ -6,18 +6,19 @@ import { validateFieldValues } from '../validator/field-values.js';
|
|
|
6
6
|
import { detectFieldTypos } from '../validator/field-typos.js';
|
|
7
7
|
import { validateFrontmatter } from '../validator/validate-frontmatter.js';
|
|
8
8
|
import { validateChatPrecedence } from '../validator/chat-precedence.js';
|
|
9
|
+
import { detectDirectivesInNonArticle } from '../validator/directives.js';
|
|
9
10
|
import { parseWikilink, hasRelativePath } from './wikilink.js';
|
|
10
11
|
import { parseTimestamp } from '../bundler/video.js';
|
|
11
12
|
// Valid segment types for lens H4 headers
|
|
12
|
-
const LENS_SEGMENT_TYPES = new Set(['text', 'chat', 'article-excerpt', 'video-excerpt', 'question']);
|
|
13
|
+
const LENS_SEGMENT_TYPES = new Set(['text', 'chat', 'article-excerpt', 'video-excerpt', 'question', 'roleplay']);
|
|
13
14
|
// Valid segment types per section output type
|
|
14
15
|
const VALID_SEGMENTS_PER_SECTION = {
|
|
15
|
-
'page': new Set(['text', 'chat', 'question']),
|
|
16
|
-
'lens-article': new Set(['text', 'chat', 'article-excerpt', 'question']),
|
|
17
|
-
'lens-video': new Set(['text', 'chat', 'video-excerpt', 'question']),
|
|
16
|
+
'page': new Set(['text', 'chat', 'question', 'roleplay']),
|
|
17
|
+
'lens-article': new Set(['text', 'chat', 'article-excerpt', 'question', 'roleplay']),
|
|
18
|
+
'lens-video': new Set(['text', 'chat', 'video-excerpt', 'question', 'roleplay']),
|
|
18
19
|
};
|
|
19
20
|
// H4 segment header pattern: #### <type> or #### <type>: <title>
|
|
20
|
-
const SEGMENT_HEADER_PATTERN = /^####\s+([^:\s]+)(?::\s*(
|
|
21
|
+
const SEGMENT_HEADER_PATTERN = /^####\s+([^:\s]+)(?::\s*(.*?))?\s*$/i;
|
|
21
22
|
// Field pattern: fieldname:: value
|
|
22
23
|
const FIELD_PATTERN = /^([\w-]+)::\s*(.*)$/;
|
|
23
24
|
/**
|
|
@@ -70,7 +71,7 @@ export function parseSegments(sectionBody, bodyStartLine, file) {
|
|
|
70
71
|
line: lineNum,
|
|
71
72
|
message: `Found '${singleColonMatch[1]}:' with single colon — did you mean '${singleColonMatch[1]}::'?`,
|
|
72
73
|
suggestion: `Change '${singleColonMatch[1]}:' to '${singleColonMatch[1]}::' (double colon)`,
|
|
73
|
-
severity: '
|
|
74
|
+
severity: 'error',
|
|
74
75
|
});
|
|
75
76
|
}
|
|
76
77
|
currentFieldLines.push(line);
|
|
@@ -161,6 +162,9 @@ export function convertSegment(raw, sectionType, file) {
|
|
|
161
162
|
};
|
|
162
163
|
return { segment, errors };
|
|
163
164
|
}
|
|
165
|
+
// Warn if directives are used in lens text segments (they only render in articles)
|
|
166
|
+
const directiveWarnings = detectDirectivesInNonArticle(content, file, raw.line);
|
|
167
|
+
errors.push(...directiveWarnings);
|
|
164
168
|
const segment = {
|
|
165
169
|
type: 'text',
|
|
166
170
|
content,
|
|
@@ -264,7 +268,7 @@ export function convertSegment(raw, sectionType, file) {
|
|
|
264
268
|
return { segment, errors };
|
|
265
269
|
}
|
|
266
270
|
case 'question': {
|
|
267
|
-
const content = raw.fields['content']
|
|
271
|
+
const content = raw.fields['content'];
|
|
268
272
|
if (!content || content.trim() === '') {
|
|
269
273
|
errors.push({
|
|
270
274
|
file,
|
|
@@ -278,7 +282,7 @@ export function convertSegment(raw, sectionType, file) {
|
|
|
278
282
|
const segment = {
|
|
279
283
|
type: 'question',
|
|
280
284
|
content,
|
|
281
|
-
assessmentInstructions: raw.fields['assessment-instructions'] ||
|
|
285
|
+
assessmentInstructions: raw.fields['assessment-instructions'] || undefined,
|
|
282
286
|
maxTime: raw.fields['max-time'] || undefined,
|
|
283
287
|
maxChars: raw.fields['max-chars'] ? parseInt(raw.fields['max-chars'], 10) : undefined,
|
|
284
288
|
enforceVoice: raw.fields['enforce-voice']?.toLowerCase() === 'true' ? true : undefined,
|
|
@@ -287,6 +291,52 @@ export function convertSegment(raw, sectionType, file) {
|
|
|
287
291
|
};
|
|
288
292
|
return { segment, errors };
|
|
289
293
|
}
|
|
294
|
+
case 'roleplay': {
|
|
295
|
+
const id = raw.fields['id'];
|
|
296
|
+
if (!id || id.trim() === '') {
|
|
297
|
+
errors.push({
|
|
298
|
+
file,
|
|
299
|
+
line: raw.line,
|
|
300
|
+
message: 'Roleplay segment missing id:: field',
|
|
301
|
+
suggestion: "Add 'id:: <uuid>' to the roleplay segment",
|
|
302
|
+
severity: 'error',
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
const content = raw.fields['content'];
|
|
306
|
+
if (!content || content.trim() === '') {
|
|
307
|
+
errors.push({
|
|
308
|
+
file,
|
|
309
|
+
line: raw.line,
|
|
310
|
+
message: 'Roleplay segment missing content:: field',
|
|
311
|
+
suggestion: "Add 'content:: Your scenario briefing here'",
|
|
312
|
+
severity: 'error',
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
const aiInstructions = raw.fields['ai-instructions'];
|
|
316
|
+
if (!aiInstructions || aiInstructions.trim() === '') {
|
|
317
|
+
errors.push({
|
|
318
|
+
file,
|
|
319
|
+
line: raw.line,
|
|
320
|
+
message: 'Roleplay segment missing ai-instructions:: field',
|
|
321
|
+
suggestion: "Add 'ai-instructions:: Character behavior description'",
|
|
322
|
+
severity: 'error',
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
if (!id || id.trim() === '' || !content || content.trim() === '' || !aiInstructions || aiInstructions.trim() === '') {
|
|
326
|
+
return { segment: null, errors };
|
|
327
|
+
}
|
|
328
|
+
const segment = {
|
|
329
|
+
type: 'roleplay',
|
|
330
|
+
id,
|
|
331
|
+
content,
|
|
332
|
+
aiInstructions,
|
|
333
|
+
openingMessage: raw.fields['opening-message'] || undefined,
|
|
334
|
+
assessmentInstructions: raw.fields['assessment-instructions'] || undefined,
|
|
335
|
+
optional: raw.fields.optional?.toLowerCase() === 'true' ? true : undefined,
|
|
336
|
+
feedback: raw.fields['feedback']?.toLowerCase() === 'true' ? true : undefined,
|
|
337
|
+
};
|
|
338
|
+
return { segment, errors };
|
|
339
|
+
}
|
|
290
340
|
default:
|
|
291
341
|
// Unknown segment type - error already reported during parseSegments
|
|
292
342
|
return { segment: null, errors };
|
|
@@ -332,6 +382,31 @@ function checkEmptySegment(raw, file) {
|
|
|
332
382
|
export function stripObsidianComments(content) {
|
|
333
383
|
return content.replace(/%%.*?%%/gs, '');
|
|
334
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* Strip CriticMarkup from content using reject-all-changes behavior:
|
|
387
|
+
* - {>>comments<<} → removed
|
|
388
|
+
* - {++additions++} → removed
|
|
389
|
+
* - {--deletions--} → inner content kept (original preserved)
|
|
390
|
+
* - {~~old~>new~~} → old text kept
|
|
391
|
+
* - {==highlights==} → inner content kept, markers removed
|
|
392
|
+
*/
|
|
393
|
+
export function stripCriticMarkup(content) {
|
|
394
|
+
return content
|
|
395
|
+
.replace(/\{>>.*?<<\}/gs, '') // Comments → remove
|
|
396
|
+
.replace(/\{\+\+.*?\+\+\}/gs, '') // Additions → remove
|
|
397
|
+
.replace(/\{--(?:\{[^}]*\}@@)?(.*?)--\}/gs, '$1') // Deletions → keep inner (skip metadata)
|
|
398
|
+
.replace(/\{~~(?:\{[^}]*\}@@)?(.*?)~>.*?~~\}/gs, '$1') // Substitutions → keep old (skip metadata)
|
|
399
|
+
.replace(/\{==(?:\{[^}]*\}@@)?(.*?)==\}/gs, '$1'); // Highlights → keep inner (skip metadata)
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Strip all authoring markup (CriticMarkup + Obsidian comments) from content.
|
|
403
|
+
* Call at the top of each parser before any processing.
|
|
404
|
+
*/
|
|
405
|
+
export function stripAuthoringMarkup(content) {
|
|
406
|
+
const stripped = stripObsidianComments(stripCriticMarkup(content));
|
|
407
|
+
// Trim trailing whitespace left by inline markup removal
|
|
408
|
+
return stripped.split('\n').map(line => line.trimEnd()).join('\n');
|
|
409
|
+
}
|
|
335
410
|
/**
|
|
336
411
|
* Parse a lens file into structured lens data.
|
|
337
412
|
*
|
|
@@ -341,8 +416,8 @@ export function stripObsidianComments(content) {
|
|
|
341
416
|
*/
|
|
342
417
|
export function parseLens(content, file) {
|
|
343
418
|
const errors = [];
|
|
344
|
-
// Strip Obsidian comments before parsing
|
|
345
|
-
content =
|
|
419
|
+
// Strip authoring markup (CriticMarkup + Obsidian comments) before parsing
|
|
420
|
+
content = stripAuthoringMarkup(content);
|
|
346
421
|
// Step 1: Parse frontmatter and validate id field
|
|
347
422
|
const frontmatterResult = parseFrontmatter(content, file);
|
|
348
423
|
if (frontmatterResult.error) {
|
|
@@ -366,6 +441,19 @@ export function parseLens(content, file) {
|
|
|
366
441
|
});
|
|
367
442
|
return { lens: null, errors };
|
|
368
443
|
}
|
|
444
|
+
const tldr = typeof frontmatter.tldr === 'string' ? frontmatter.tldr : undefined;
|
|
445
|
+
if (tldr) {
|
|
446
|
+
const wordCount = tldr.trim().split(/\s+/).filter(Boolean).length;
|
|
447
|
+
if (wordCount > 80) {
|
|
448
|
+
errors.push({
|
|
449
|
+
file,
|
|
450
|
+
line: 2,
|
|
451
|
+
message: `tldr exceeds 80 words (has ${wordCount})`,
|
|
452
|
+
suggestion: 'Shorten the tldr to 80 words or fewer',
|
|
453
|
+
severity: 'error',
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
369
457
|
// Step 2: Parse H3 sections (Text, Article, Video)
|
|
370
458
|
const sectionsResult = parseSections(body, 3, LENS_SECTION_TYPES, file);
|
|
371
459
|
// Adjust line numbers to account for frontmatter
|
|
@@ -494,6 +582,7 @@ export function parseLens(content, file) {
|
|
|
494
582
|
}
|
|
495
583
|
const lens = {
|
|
496
584
|
id: frontmatter.id,
|
|
585
|
+
tldr,
|
|
497
586
|
sections: parsedSections,
|
|
498
587
|
};
|
|
499
588
|
return { lens, errors };
|