Skip to content

Commit 867a5b9

Browse files
ofriwclaude
andcommitted
Add prompt example preservation rules and validation to generator
Enforces that prompt examples (text showing what to write to an AI) must be preserved as code blocks, never converted to bullet points. Adds validatePromptExamples() function that fails the build if prompts are paraphrased. Includes comprehensive documentation for: - Strategic line break formatting (60 char limit) - Prompt detection patterns - codeComparison usage for prompt examples - JSON string escaping with \n 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent a4aaa3e commit 867a5b9

File tree

1 file changed

+244
-4
lines changed

1 file changed

+244
-4
lines changed

scripts/generate-presentation.js

Lines changed: 244 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,11 @@ SLIDE TYPES:
119119
1. **Title Slide**: Lesson title, learning objectives
120120
2. **Concept Slide**: Key idea with 3-5 bullet points
121121
3. **Code Example Slide**: Code snippet with context
122-
4. **Code Execution Slide**: Step-by-step visualization of execution flows (agent loops, algorithms, workflows)
123-
5. **Comparison Slide**: Effective vs ineffective patterns
124-
6. **Visual Slide**: Custom component (CapabilityMatrix, etc.)
125-
7. **Key Takeaway Slide**: Summary of section or lesson
122+
4. **Code Comparison Slide**: Side-by-side code examples (especially for prompt examples)
123+
5. **Code Execution Slide**: Step-by-step visualization of execution flows (agent loops, algorithms, workflows)
124+
6. **Comparison Slide**: Effective vs ineffective patterns (bullet points)
125+
7. **Visual Slide**: Custom component (CapabilityMatrix, etc.)
126+
8. **Key Takeaway Slide**: Summary of section or lesson
126127
127128
HANDLING CODE BLOCKS:
128129
@@ -141,6 +142,124 @@ For presentation slides:
141142
✗ Don't include every code example from the lesson
142143
✗ Don't show code without explaining its purpose
143144
145+
CODE FORMATTING FOR PRESENTATIONS:
146+
147+
CRITICAL: Add strategic line breaks to prevent horizontal clipping on slides.
148+
149+
For ALL code examples and prompt text in slides:
150+
✓ Keep lines under 60 characters for readability in presentation view
151+
✓ Break at logical points:
152+
- After commas in parameter lists
153+
- After operators (&&, ||, =, etc.)
154+
- After colons in object literals
155+
- Between method chains (one per line)
156+
- After opening statements before conditions
157+
✓ Maintain proper indentation for wrapped lines (2-4 spaces)
158+
✓ Preserve semantic meaning - don't break mid-identifier or mid-word
159+
✓ Use \\n escape sequence for line breaks in JSON strings (NOT literal newlines)
160+
161+
IMPORTANT: Since you're generating JSON output, use \\n for line breaks.
162+
163+
When you write the JSON output, use \\n in string literals (escaped backslash-n).
164+
This will be parsed as a newline character (\n) when the JSON is read.
165+
166+
Examples showing what to OUTPUT in your generated JSON:
167+
168+
BEFORE (too long):
169+
"Write a TypeScript function that validates email addresses per RFC 5322"
170+
171+
AFTER (with \\n breaks in the output):
172+
"Write a TypeScript function that validates\\nemail addresses per RFC 5322"
173+
174+
BEFORE (code too long):
175+
"const result = validateEmail(userInput) && checkDomain(domain) && verifyMX(server);"
176+
177+
AFTER (with \\n breaks in the output):
178+
"const result = validateEmail(userInput) &&\\n checkDomain(domain) &&\\n verifyMX(server);"
179+
180+
Apply strategic line breaks using \\n to:
181+
- Prompt examples in codeComparison slides (both leftCode and rightCode)
182+
- Code snippets in code slides
183+
- Command examples
184+
- Text content in comparison slides if lines exceed 60 characters
185+
186+
TECHNICAL NOTE: When you output \\n in a JSON string, it represents an escaped newline.
187+
After JSON parsing, it becomes a single \n (newline character in the string value).
188+
DO NOT output literal newlines in JSON strings - always use the \\n escape sequence.
189+
This prevents horizontal scrolling and ensures all content is visible without clipping.
190+
191+
<MANDATORY_RULES>
192+
CRITICAL: PRESERVING PROMPT EXAMPLES
193+
194+
When the source material includes prompt examples (text showing what to write to an AI coding assistant):
195+
✓ PRESERVE EXACTLY as shown—do NOT paraphrase, rewrite, or summarize
196+
✓ Use "code" or "codeComparison" slide types, NEVER bullet points
197+
✓ Set language to "text" or "markdown" for prompt examples
198+
✓ Include the FULL prompt text with exact formatting and line breaks
199+
✓ Copy verbatim from source—these are educational examples showing structure
200+
201+
Examples of prompt text that MUST be preserved as code:
202+
- "Write a TypeScript function that validates email addresses..."
203+
- "You are a security engineer. Review this code for..."
204+
- "Calculate the optimal cache size for 1M users..."
205+
206+
For comparison slides showing ineffective vs effective prompts:
207+
✓ Use "codeComparison" type with leftCode/rightCode fields
208+
✓ Set language to "text" for both sides
209+
✓ Copy the EXACT prompt text from the source markdown
210+
✗ Do NOT convert prompts to bullet points
211+
✗ Do NOT summarize or paraphrase prompt text
212+
✗ Do NOT rewrite for "presentation style"—preserve authenticity
213+
214+
Example structure for prompt comparisons:
215+
{
216+
"type": "codeComparison",
217+
"title": "Imperative Commands: Ineffective vs Effective",
218+
"leftCode": {
219+
"label": "Ineffective",
220+
"language": "text",
221+
"code": "Could you help me write a function to validate email addresses?\nThanks in advance!"
222+
},
223+
"rightCode": {
224+
"label": "Effective",
225+
"language": "text",
226+
"code": "Write a TypeScript function that validates email addresses per RFC 5322.\nHandle edge cases:\n- Multiple @ symbols (invalid)\n- Missing domain (invalid)\n- Plus addressing (valid)\n\nReturn { valid: boolean, reason?: string }"
227+
},
228+
"speakerNotes": { ... }
229+
}
230+
</MANDATORY_RULES>
231+
232+
COMMON MISTAKE - DO NOT DO THIS:
233+
234+
❌ WRONG - Converting prompts to bullet points:
235+
{
236+
"type": "concept",
237+
"title": "Action Verbs and Specificity",
238+
"content": [
239+
"Write (not make) → establishes code pattern",
240+
"Debug X in File.ts:47 (not fix) → pinpoints scope",
241+
"Add JSDoc to exported functions (not improve docs) → defines scope"
242+
]
243+
}
244+
245+
✅ CORRECT - Using codeComparison for prompts:
246+
{
247+
"type": "codeComparison",
248+
"title": "Action Verbs and Specificity",
249+
"leftCode": {
250+
"label": "Weak",
251+
"language": "text",
252+
"code": "Make a function\nFix the bug\nUpdate the docs\nImprove performance"
253+
},
254+
"rightCode": {
255+
"label": "Strong",
256+
"language": "text",
257+
"code": "Write a function\nDebug the null pointer exception in UserService.ts:47\nAdd JSDoc comments to all exported functions in auth.ts\nOptimize the query to use indexed columns"
258+
}
259+
}
260+
261+
If you see prompt examples in the source (text showing what to write to an AI), you MUST use "code" or "codeComparison" slide types. NEVER use "concept" with bullet points for prompts.
262+
144263
COMPONENT DETECTION (CRITICAL):
145264
146265
The source content contains markers for visual React components in the format:
@@ -277,6 +396,8 @@ OUTPUT FORMAT:
277396
278397
You must generate a valid JSON file with this structure:
279398
399+
REMINDER: If the source contains prompt examples (text showing what to write to an AI coding assistant), you MUST use "code" or "codeComparison" slide types with language="text". NEVER convert prompts to bullet points in "concept" slides.
400+
280401
{
281402
"metadata": {
282403
"title": "Lesson Title",
@@ -390,6 +511,21 @@ INCORRECT: Putting the better option on the left will show it with RED ✗ styli
390511
},
391512
"speakerNotes": { ... }
392513
},
514+
{
515+
"type": "codeComparison",
516+
"title": "Prompt Example: Ineffective vs Effective",
517+
"leftCode": {
518+
"label": "Ineffective", // MANDATORY: LEFT = worse prompt (RED ✗)
519+
"language": "text",
520+
"code": "Could you help me write a function?\nThanks!"
521+
},
522+
"rightCode": {
523+
"label": "Effective", // MANDATORY: RIGHT = better prompt (GREEN ✓)
524+
"language": "text",
525+
"code": "Write a TypeScript function that validates email addresses.\nHandle edge cases:\n- Invalid @ symbols\n- Missing domain\n\nReturn { valid: boolean }"
526+
},
527+
"speakerNotes": { ... }
528+
},
393529
{
394530
"type": "marketingReality",
395531
"title": "Marketing vs Reality: What Actually Happens",
@@ -431,6 +567,15 @@ CRITICAL REQUIREMENTS:
431567
4. Every slide MUST have speakerNotes with all fields
432568
5. Code examples must be actual code from the lesson, not pseudocode
433569
6. Content arrays must have 3-5 items (except title slide)
570+
7. PROMPT EXAMPLES: Use "code" or "codeComparison" slide types, NEVER bullet points
571+
572+
BEFORE YOU GENERATE - CHECKLIST:
573+
574+
□ Did I identify all prompt examples in the source?
575+
□ Will I use "codeComparison" type for those slides (NOT "concept")?
576+
□ Will I set language="text" for prompt code blocks?
577+
□ Will I copy the EXACT prompt text without paraphrasing?
578+
□ Did I avoid converting prompts to explanatory bullet points?
434579
435580
TECHNICAL CONTENT TITLE: ${fileName}
436581
@@ -690,6 +835,77 @@ function validateComparisonSemantics(presentation) {
690835
};
691836
}
692837

838+
/**
839+
* Validate that prompt examples are preserved as code blocks
840+
* @param {string} content - Parsed markdown content
841+
* @param {object} presentation - Generated presentation object
842+
* @returns {object} Validation result with issues
843+
*/
844+
function validatePromptExamples(content, presentation) {
845+
// Shared regex patterns for detecting prompt-like content
846+
// Common action verbs that indicate AI assistant commands
847+
const PROMPT_VERBS = 'Write |You are |Calculate |Review |Debug |Add |Create |Implement |Refactor ';
848+
849+
// Detect prompt examples in fenced code blocks
850+
const promptInCodeBlocks = new RegExp(`\`\`\`[^\\n]*\\n(${PROMPT_VERBS})`, 'gi');
851+
const hasPromptExamples = promptInCodeBlocks.test(content);
852+
853+
if (!hasPromptExamples) {
854+
return { valid: true, issues: [], hasPromptExamples: false };
855+
}
856+
857+
const codeSlides = presentation.slides.filter(s =>
858+
s.type === 'code' || s.type === 'codeComparison'
859+
);
860+
861+
const issues = [];
862+
863+
if (codeSlides.length === 0) {
864+
issues.push('Source contains prompt examples but no code/codeComparison slides were generated');
865+
}
866+
867+
// Pattern for detecting prompts in bullet points (should trigger codeComparison)
868+
const promptLikeContent = new RegExp(`${PROMPT_VERBS}|Return \\{`, 'i');
869+
870+
// Pattern for detecting full prompt sentences (more specific check)
871+
const fullPromptPattern = /Write [a-z]+ [a-z]+ function|You are a [a-z]+ engineer|Calculate the [a-z]+ [a-z]+|Review this [a-z]+ code/i;
872+
873+
// Check if any comparison slides have prompt-like content that should be codeComparison
874+
const comparisonSlides = presentation.slides.filter(s => s.type === 'comparison');
875+
for (const slide of comparisonSlides) {
876+
if (slide.left?.content || slide.right?.content) {
877+
const leftContent = slide.left?.content?.join(' ') || '';
878+
const rightContent = slide.right?.content?.join(' ') || '';
879+
const combinedContent = leftContent + ' ' + rightContent;
880+
881+
// Check if content looks like prompt examples
882+
if (promptLikeContent.test(combinedContent)) {
883+
issues.push(`Slide "${slide.title}" appears to contain prompt examples as bullet points - should use codeComparison type`);
884+
}
885+
}
886+
}
887+
888+
// Check if any concept slides have prompt-like content that should be code/codeComparison
889+
const conceptSlides = presentation.slides.filter(s => s.type === 'concept');
890+
for (const slide of conceptSlides) {
891+
if (slide.content && Array.isArray(slide.content)) {
892+
const combinedContent = slide.content.join(' ');
893+
894+
// Check if content looks like prompt examples (full sentences/commands, not just explanatory text)
895+
if (fullPromptPattern.test(combinedContent)) {
896+
issues.push(`Slide "${slide.title}" appears to contain prompt examples as bullet points - should use code or codeComparison type`);
897+
}
898+
}
899+
}
900+
901+
return {
902+
valid: issues.length === 0,
903+
issues,
904+
hasPromptExamples,
905+
codeSlideCount: codeSlides.length
906+
};
907+
}
908+
693909
/**
694910
* Generate presentation for a file
695911
*/
@@ -732,6 +948,13 @@ async function generatePresentation(filePath, manifest, config) {
732948
// Generate presentation using Claude
733949
const presentation = await generatePresentationWithClaude(prompt, outputPath);
734950

951+
// ========================================================================
952+
// POST-GENERATION VALIDATION
953+
// These validations run AFTER Claude generates the JSON to ensure quality.
954+
// They check that the LLM followed the prompt instructions correctly.
955+
// Visual components and comparisons generate warnings; prompts cause errors.
956+
// ========================================================================
957+
735958
// Validate that all visual components were included
736959
const validation = validateComponents(content, presentation);
737960
if (!validation.allPresent) {
@@ -757,6 +980,23 @@ async function generatePresentation(filePath, manifest, config) {
757980
console.log(` ✅ All ${semanticValidation.totalComparisons} comparison slide(s) follow correct convention`);
758981
}
759982

983+
// Validate prompt examples are preserved as code
984+
// CRITICAL: This validation is intentionally strict and throws an error because
985+
// prompt examples are core educational content that must be preserved exactly.
986+
// If the LLM converts prompts to bullet points, it loses pedagogical value.
987+
const promptValidation = validatePromptExamples(content, presentation);
988+
if (!promptValidation.valid) {
989+
console.log(` ❌ BUILD FAILURE: ${promptValidation.issues.length} prompt example issue(s):`);
990+
promptValidation.issues.forEach(issue => {
991+
console.log(` - ${issue}`);
992+
});
993+
console.log(` ℹ️ Prompt examples MUST use "code" or "codeComparison" slide types, NOT bullet points`);
994+
console.log(` ℹ️ The presentation was not saved. Fix the generation and try again.`);
995+
throw new Error('Prompt validation failed - prompt examples were converted to bullet points instead of code blocks');
996+
} else if (promptValidation.hasPromptExamples) {
997+
console.log(` ✅ All prompt examples preserved as code blocks (${promptValidation.codeSlideCount} code slide(s))`);
998+
}
999+
7601000
// Copy to static directory for deployment
7611001
const staticPath = join(STATIC_OUTPUT_DIR, dirname(relativePath), outputFileName);
7621002
mkdirSync(dirname(staticPath), { recursive: true });

0 commit comments

Comments
 (0)