Skip to content

Commit 86f61b5

Browse files
ofriwclaude
andcommitted
Merge podcast branch: combine automated line-breaking with validation framework
Resolved architectural conflict between manual prompting (main) and automated post-processing (podcast) approaches to line-breaking. Merge strategy: - Parser: Combined both parameter needs (mode + preserveCode) - Architecture: Preserved podcast's separation of concerns - Quality: Integrated main's 5 validation functions - Presentations: Accepted podcast versions (will regenerate) Result: Clean generation → validation → automated formatting pipeline 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2 parents f4c96f2 + 576eb5a commit 86f61b5

File tree

5 files changed

+303
-390
lines changed

5 files changed

+303
-390
lines changed

scripts/generate-presentation.js

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -723,21 +723,11 @@ async function generatePresentationWithClaude(prompt, outputPath) {
723723

724724
console.log(` ✅ Valid presentation JSON (${presentation.slides.length} slides)`);
725725

726-
// Apply deterministic line breaking
727-
console.log(' 🔧 Applying line breaking...');
728-
const { presentation: processedPresentation, stats } = processPresentation(presentation);
729-
730-
// Log statistics
731-
if (stats.linesShortened > 0) {
732-
console.log(` ✂️ Fixed ${stats.linesShortened} long lines (max reduction: ${stats.maxReduction} chars)`);
733-
} else {
734-
console.log(' ✅ No lines exceeded 60 characters');
735-
}
736-
737-
// Write back the processed presentation
738-
writeFileSync(outputPath, JSON.stringify(processedPresentation, null, 2), 'utf-8');
726+
// Write the unmodified presentation to file for validation
727+
// Line breaking will happen after validation passes
728+
writeFileSync(outputPath, JSON.stringify(presentation, null, 2), 'utf-8');
739729

740-
resolve(processedPresentation);
730+
resolve(presentation);
741731
} catch (parseError) {
742732
reject(new Error(`Failed to parse JSON: ${parseError.message}\nContent preview: ${fileContent?.slice(0, 200)}`));
743733
return;
@@ -1182,8 +1172,8 @@ async function generatePresentation(filePath, manifest, config) {
11821172
console.log(`\n📄 Generating presentation: ${relativePath}`);
11831173

11841174
try {
1185-
// Parse content in presentation mode
1186-
const content = parseMarkdownContent(filePath, 'presentation');
1175+
// Parse content in presentation mode, preserving code blocks for validation
1176+
const content = parseMarkdownContent(filePath, 'presentation', true);
11871177

11881178
if (content.length < 100) {
11891179
console.log(` ⚠️ Skipping - content too short`);
@@ -1298,24 +1288,38 @@ async function generatePresentation(filePath, manifest, config) {
12981288
console.log(` ✅ All ${codeSourceValidation.codeSlidesChecked} code slide(s) verified against source`);
12991289
}
13001290

1291+
// Apply deterministic line breaking (AFTER validation passes)
1292+
console.log(' 🔧 Applying line breaking...');
1293+
const { presentation: processedPresentation, stats } = processPresentation(presentation);
1294+
1295+
// Log statistics
1296+
if (stats.linesShortened > 0) {
1297+
console.log(` ✂️ Fixed ${stats.linesShortened} long lines (max reduction: ${stats.maxReduction} chars)`);
1298+
} else {
1299+
console.log(' ✅ No lines exceeded 60 characters');
1300+
}
1301+
1302+
// Write the line-broken version back to output file
1303+
writeFileSync(outputPath, JSON.stringify(processedPresentation, null, 2), 'utf-8');
1304+
13011305
// Copy to static directory for deployment
13021306
const staticPath = join(STATIC_OUTPUT_DIR, dirname(relativePath), outputFileName);
13031307
mkdirSync(dirname(staticPath), { recursive: true });
1304-
writeFileSync(staticPath, JSON.stringify(presentation, null, 2), 'utf-8');
1308+
writeFileSync(staticPath, JSON.stringify(processedPresentation, null, 2), 'utf-8');
13051309

13061310
// Update manifest
13071311
const presentationUrl = `/presentations/${join(dirname(relativePath), outputFileName)}`;
13081312
manifest[relativePath] = {
13091313
presentationUrl,
1310-
slideCount: presentation.slides.length,
1311-
estimatedDuration: presentation.metadata.estimatedDuration,
1312-
title: presentation.metadata.title,
1314+
slideCount: processedPresentation.slides.length,
1315+
estimatedDuration: processedPresentation.metadata.estimatedDuration,
1316+
title: processedPresentation.metadata.title,
13131317
generatedAt: new Date().toISOString()
13141318
};
13151319

13161320
console.log(` ✅ Generated: ${presentationUrl}`);
1317-
console.log(` 📊 Slides: ${presentation.slides.length}`);
1318-
console.log(` ⏱️ Duration: ${presentation.metadata.estimatedDuration}`);
1321+
console.log(` 📊 Slides: ${processedPresentation.slides.length}`);
1322+
console.log(` ⏱️ Duration: ${processedPresentation.metadata.estimatedDuration}`);
13191323

13201324
return outputPath;
13211325

scripts/lib/markdown-parser.js

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,10 @@ export function extractCodeSummary(code, language) {
121121
* Parse MDX/MD file and extract clean text content
122122
* @param {string} filePath - Path to MDX/MD file
123123
* @param {string} mode - Rendering mode: 'doc' (default) or 'presentation'
124-
* @returns {string} Cleaned text content with code blocks described
124+
* @param {boolean} preserveCode - If true, keeps code blocks intact instead of converting to descriptions
125+
* @returns {string} Cleaned text content with code blocks described (or preserved if preserveCode=true)
125126
*/
126-
export function parseMarkdownContent(filePath, mode = 'doc') {
127+
export function parseMarkdownContent(filePath, mode = 'doc', preserveCode = false) {
127128
const content = readFileSync(filePath, 'utf-8');
128129

129130
// Remove frontmatter
@@ -151,42 +152,45 @@ export function parseMarkdownContent(filePath, mode = 'doc') {
151152
// Remove remaining HTML tags (lowercase = HTML elements)
152153
cleaned = cleaned.replace(/<[^>]+>/g, '');
153154

154-
// First pass: Find all code blocks and their contexts
155-
const codeBlocks = [];
156-
const regex = /```[\s\S]*?```/g;
157-
let match;
158-
159-
while ((match = regex.exec(cleaned)) !== null) {
160-
const precedingStart = Math.max(0, match.index - 200);
161-
const precedingContext = cleaned.substring(precedingStart, match.index);
162-
163-
const followingEnd = Math.min(cleaned.length, match.index + match[0].length + 200);
164-
const followingContext = cleaned.substring(match.index + match[0].length, followingEnd);
155+
// Code block processing: preserve or describe based on mode
156+
if (!preserveCode) {
157+
// First pass: Find all code blocks and their contexts
158+
const codeBlocks = [];
159+
const regex = /```[\s\S]*?```/g;
160+
let match;
161+
162+
while ((match = regex.exec(cleaned)) !== null) {
163+
const precedingStart = Math.max(0, match.index - 200);
164+
const precedingContext = cleaned.substring(precedingStart, match.index);
165+
166+
const followingEnd = Math.min(cleaned.length, match.index + match[0].length + 200);
167+
const followingContext = cleaned.substring(match.index + match[0].length, followingEnd);
168+
169+
codeBlocks.push({
170+
original: match[0],
171+
index: match.index,
172+
precedingContext,
173+
followingContext
174+
});
175+
}
165176

166-
codeBlocks.push({
167-
original: match[0],
168-
index: match.index,
169-
precedingContext,
170-
followingContext
171-
});
172-
}
177+
// Second pass: Replace code blocks with descriptions
178+
let offset = 0;
179+
for (const block of codeBlocks) {
180+
const description = describeCodeBlock(block.original, block.precedingContext, block.followingContext);
181+
const adjustedIndex = block.index + offset;
173182

174-
// Second pass: Replace code blocks with descriptions
175-
let offset = 0;
176-
for (const block of codeBlocks) {
177-
const description = describeCodeBlock(block.original, block.precedingContext, block.followingContext);
178-
const adjustedIndex = block.index + offset;
183+
cleaned = cleaned.substring(0, adjustedIndex) +
184+
description +
185+
cleaned.substring(adjustedIndex + block.original.length);
179186

180-
cleaned = cleaned.substring(0, adjustedIndex) +
181-
description +
182-
cleaned.substring(adjustedIndex + block.original.length);
187+
offset += description.length - block.original.length;
188+
}
183189

184-
offset += description.length - block.original.length;
190+
// Remove inline code (only when converting code blocks to descriptions)
191+
cleaned = cleaned.replace(/`[^`]+`/g, (match) => match.replace(/`/g, ''));
185192
}
186193

187-
// Remove inline code
188-
cleaned = cleaned.replace(/`[^`]+`/g, (match) => match.replace(/`/g, ''));
189-
190194
// Remove images
191195
cleaned = cleaned.replace(/!\[.*?\]\(.*?\)/g, '[Image]');
192196

website/static/presentations/manifest.json

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,78 +2,78 @@
22
"intro.md": {
33
"presentationUrl": "/presentations/intro.json",
44
"slideCount": 11,
5-
"estimatedDuration": "25-35 minutes",
6-
"title": "AI Coding Assistants: Operator Training",
7-
"generatedAt": "2025-11-10T18:21:59.791Z"
5+
"estimatedDuration": "35-45 minutes",
6+
"title": "Operator Training: AI Coding Assistants in Production",
7+
"generatedAt": "2025-11-09T10:34:22.957Z"
88
},
99
"methodology/lesson-3-high-level-methodology.md": {
1010
"presentationUrl": "/presentations/methodology/lesson-3-high-level-methodology.json",
1111
"slideCount": 12,
1212
"estimatedDuration": "45-60 minutes",
13-
"title": "High-Level Methodology: Research, Plan, Execute, Validate",
14-
"generatedAt": "2025-11-10T18:22:58.400Z"
13+
"title": "Lesson 3: High-Level Methodology",
14+
"generatedAt": "2025-11-10T15:59:19.399Z"
1515
},
1616
"methodology/lesson-4-prompting-101.md": {
1717
"presentationUrl": "/presentations/methodology/lesson-4-prompting-101.json",
18-
"slideCount": 12,
19-
"estimatedDuration": "45-60 minutes",
20-
"title": "Lesson 4: Prompting 101",
21-
"generatedAt": "2025-11-10T18:24:08.971Z"
18+
"slideCount": 11,
19+
"estimatedDuration": "40-50 minutes",
20+
"title": "Prompting 101: Pattern Completion, Not Conversation",
21+
"generatedAt": "2025-11-10T19:54:29.185Z"
2222
},
2323
"methodology/lesson-5-grounding.md": {
2424
"presentationUrl": "/presentations/methodology/lesson-5-grounding.json",
25-
"slideCount": 11,
25+
"slideCount": 12,
2626
"estimatedDuration": "40-50 minutes",
27-
"title": "Lesson 5: Grounding - Anchoring Agents in Reality",
28-
"generatedAt": "2025-11-10T18:25:26.293Z"
27+
"title": "Lesson 5: Grounding - Keeping AI Agents Tethered to Reality",
28+
"generatedAt": "2025-11-09T06:35:22.963Z"
2929
},
3030
"practical-techniques/lesson-10-debugging.md": {
3131
"presentationUrl": "/presentations/practical-techniques/lesson-10-debugging.json",
32-
"slideCount": 10,
33-
"estimatedDuration": "40-50 minutes",
32+
"slideCount": 11,
33+
"estimatedDuration": "35-45 minutes",
3434
"title": "Debugging with AI Agents",
35-
"generatedAt": "2025-11-10T18:26:32.394Z"
35+
"generatedAt": "2025-11-10T11:19:45.844Z"
3636
},
3737
"practical-techniques/lesson-6-project-onboarding.md": {
3838
"presentationUrl": "/presentations/practical-techniques/lesson-6-project-onboarding.json",
39-
"slideCount": 12,
40-
"estimatedDuration": "35-40 minutes",
41-
"title": "Lesson 6: Project Onboarding",
42-
"generatedAt": "2025-11-10T18:27:37.680Z"
39+
"slideCount": 11,
40+
"estimatedDuration": "35-45 minutes",
41+
"title": "Lesson 6: Project Onboarding - Context Files & AI Memory",
42+
"generatedAt": "2025-11-09T06:37:14.278Z"
4343
},
4444
"practical-techniques/lesson-7-planning-execution.md": {
4545
"presentationUrl": "/presentations/practical-techniques/lesson-7-planning-execution.json",
4646
"slideCount": 15,
47-
"estimatedDuration": "45-60 minutes",
48-
"title": "Lesson 7: Planning and Execution",
49-
"generatedAt": "2025-11-10T18:28:53.875Z"
47+
"estimatedDuration": "45-50 minutes",
48+
"title": "Lesson 7: Planning & Execution",
49+
"generatedAt": "2025-11-10T16:44:27.800Z"
5050
},
5151
"practical-techniques/lesson-8-tests-as-guardrails.md": {
5252
"presentationUrl": "/presentations/practical-techniques/lesson-8-tests-as-guardrails.json",
53-
"slideCount": 13,
54-
"estimatedDuration": "35-45 minutes",
53+
"slideCount": 11,
54+
"estimatedDuration": "40-50 minutes",
5555
"title": "Tests as Guardrails",
56-
"generatedAt": "2025-11-10T19:00:40.895Z"
56+
"generatedAt": "2025-11-10T19:55:47.621Z"
5757
},
5858
"practical-techniques/lesson-9-reviewing-code.md": {
5959
"presentationUrl": "/presentations/practical-techniques/lesson-9-reviewing-code.json",
60-
"slideCount": 11,
61-
"estimatedDuration": "45-60 minutes",
60+
"slideCount": 12,
61+
"estimatedDuration": "35-45 minutes",
6262
"title": "Lesson 9: Reviewing Code",
63-
"generatedAt": "2025-11-10T18:31:02.571Z"
63+
"generatedAt": "2025-11-10T16:45:53.163Z"
6464
},
6565
"understanding-the-tools/lesson-1-intro.md": {
6666
"presentationUrl": "/presentations/understanding-the-tools/lesson-1-intro.json",
6767
"slideCount": 11,
68-
"estimatedDuration": "35-45 minutes",
69-
"title": "Understanding AI-Driven Software Engineering",
70-
"generatedAt": "2025-11-10T18:32:03.187Z"
68+
"estimatedDuration": "40-50 minutes",
69+
"title": "Understanding the Tools: Lesson 1 - Introduction to AI Agents",
70+
"generatedAt": "2025-11-09T11:14:16.616Z"
7171
},
7272
"understanding-the-tools/lesson-2-understanding-agents.md": {
7373
"presentationUrl": "/presentations/understanding-the-tools/lesson-2-understanding-agents.json",
74-
"slideCount": 11,
75-
"estimatedDuration": "35-50 minutes",
74+
"slideCount": 12,
75+
"estimatedDuration": "35-45 minutes",
7676
"title": "Understanding Agents",
77-
"generatedAt": "2025-11-10T17:58:00.131Z"
77+
"generatedAt": "2025-11-10T14:57:50.229Z"
7878
}
7979
}

0 commit comments

Comments
 (0)