Skip to content

Commit 9d12278

Browse files
committed
Merge branch 'podcast'
2 parents 86f61b5 + 8f7ee0f commit 9d12278

File tree

4 files changed

+335
-135
lines changed

4 files changed

+335
-135
lines changed

scripts/audit-presentations.js

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ const __dirname = dirname(__filename);
1313

1414
const MIN_ITEMS = 3;
1515
const MAX_ITEMS = 5;
16+
const MAX_WORDS = 5;
1617

1718
function auditPresentation(filePath) {
1819
const content = readFileSync(filePath, 'utf-8');
1920
const presentation = JSON.parse(content);
2021
const violations = [];
22+
const wordCountViolations = [];
2123

2224
// Check slides with content arrays
2325
const slidesWithContent = presentation.slides.filter(slide => {
@@ -71,9 +73,45 @@ function auditPresentation(filePath) {
7173
}
7274
}
7375

76+
// Check takeaway word counts
77+
const takeawaySlides = presentation.slides.filter(s => s.type === 'takeaway');
78+
for (const slide of takeawaySlides) {
79+
if (slide.content && Array.isArray(slide.content)) {
80+
slide.content.forEach((item, index) => {
81+
const wordCount = item.trim().split(/\s+/).length;
82+
if (wordCount > MAX_WORDS) {
83+
wordCountViolations.push({
84+
type: 'takeaway',
85+
slide: slide.title,
86+
index: index + 1,
87+
wordCount,
88+
content: item,
89+
excess: wordCount - MAX_WORDS
90+
});
91+
}
92+
});
93+
}
94+
}
95+
96+
// Check learning objectives word counts
97+
const objectives = presentation.metadata?.learningObjectives || [];
98+
objectives.forEach((objective, index) => {
99+
const wordCount = objective.trim().split(/\s+/).length;
100+
if (wordCount > MAX_WORDS) {
101+
wordCountViolations.push({
102+
type: 'objective',
103+
index: index + 1,
104+
wordCount,
105+
content: objective,
106+
excess: wordCount - MAX_WORDS
107+
});
108+
}
109+
});
110+
74111
return {
75112
title: presentation.metadata?.title || 'Unknown',
76113
violations,
114+
wordCountViolations,
77115
totalSlides: presentation.slides.length
78116
};
79117
}
@@ -94,30 +132,57 @@ function main() {
94132
'understanding-the-tools/lesson-2-understanding-agents.json'
95133
];
96134

97-
console.log('📊 Auditing presentations for content array violations (3-5 items rule)\n');
135+
console.log('📊 Auditing presentations for violations\n');
136+
console.log('Checking:');
137+
console.log(' • Content arrays (3-5 items rule)');
138+
console.log(' • Takeaway word counts (5 words max)');
139+
console.log(' • Learning objectives word counts (5 words max)\n');
98140

99141
const results = [];
100142
let totalViolations = 0;
143+
let totalWordCountViolations = 0;
101144

102145
for (const file of files) {
103146
const filePath = join(presentationsDir, file);
104147
try {
105148
const result = auditPresentation(filePath);
106149
results.push({ file, ...result });
107150

108-
if (result.violations.length > 0) {
109-
totalViolations += result.violations.length;
151+
const hasViolations = result.violations.length > 0;
152+
const hasWordViolations = result.wordCountViolations.length > 0;
153+
154+
if (hasViolations || hasWordViolations) {
110155
console.log(`❌ ${file}`);
111156
console.log(` Title: ${result.title}`);
112-
result.violations.forEach(v => {
113-
console.log(` - "${v.slide}" (${v.type}): ${v.count} items`);
114-
if (v.count <= 8) {
115-
v.items.forEach(item => {
116-
const truncated = item.length > 60 ? item.substring(0, 57) + '...' : item;
117-
console.log(` • ${truncated}`);
118-
});
119-
}
120-
});
157+
158+
if (hasViolations) {
159+
totalViolations += result.violations.length;
160+
console.log(` Content array violations (${result.violations.length}):`);
161+
result.violations.forEach(v => {
162+
console.log(` - "${v.slide}" (${v.type}): ${v.count} items`);
163+
if (v.count <= 8) {
164+
v.items.forEach(item => {
165+
const truncated = item.length > 60 ? item.substring(0, 57) + '...' : item;
166+
console.log(` • ${truncated}`);
167+
});
168+
}
169+
});
170+
}
171+
172+
if (hasWordViolations) {
173+
totalWordCountViolations += result.wordCountViolations.length;
174+
console.log(` Word count violations (${result.wordCountViolations.length}):`);
175+
result.wordCountViolations.forEach(v => {
176+
if (v.type === 'takeaway') {
177+
console.log(` - Takeaway "${v.slide}" item ${v.index}: ${v.wordCount} words (+${v.excess})`);
178+
} else {
179+
console.log(` - Learning objective ${v.index}: ${v.wordCount} words (+${v.excess})`);
180+
}
181+
const truncated = v.content.length > 60 ? v.content.substring(0, 57) + '...' : v.content;
182+
console.log(` "${truncated}"`);
183+
});
184+
}
185+
121186
console.log('');
122187
}
123188
} catch (error) {
@@ -127,18 +192,26 @@ function main() {
127192

128193
// Summary
129194
console.log('═══════════════════════════════════════════════════════════');
130-
const violatingFiles = results.filter(r => r.violations.length > 0);
195+
const violatingFiles = results.filter(r => r.violations.length > 0 || r.wordCountViolations.length > 0);
131196

132197
if (violatingFiles.length === 0) {
133198
console.log('✅ All presentations pass validation!');
134199
} else {
135200
console.log(`\n📋 SUMMARY:\n`);
136201
console.log(`Total files audited: ${results.length}`);
137202
console.log(`Files with violations: ${violatingFiles.length}`);
138-
console.log(`Total violations: ${totalViolations}\n`);
203+
console.log(` • Content array violations: ${totalViolations}`);
204+
console.log(` • Word count violations: ${totalWordCountViolations}`);
205+
console.log(` • Total: ${totalViolations + totalWordCountViolations}\n`);
139206
console.log('Files needing regeneration:');
140207
violatingFiles.forEach(r => {
141-
console.log(` - ${r.file} (${r.violations.length} violation(s))`);
208+
const arrayViolations = r.violations.length;
209+
const wordViolations = r.wordCountViolations.length;
210+
const total = arrayViolations + wordViolations;
211+
const details = [];
212+
if (arrayViolations > 0) details.push(`${arrayViolations} array`);
213+
if (wordViolations > 0) details.push(`${wordViolations} word`);
214+
console.log(` - ${r.file} (${total} total: ${details.join(', ')})`);
142215
});
143216
}
144217
console.log('═══════════════════════════════════════════════════════════');

scripts/generate-presentation.js

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,9 @@ PRESENTATION STRUCTURE REQUIREMENTS:
109109
✓ DO: Preserve important code examples as slide content
110110
✓ DO: Identify which visual components to use (CapabilityMatrix, UShapeAttentionCurve, WorkflowCircle, GroundingComparison, ContextWindowMeter, AbstractShapesVisualization, etc.)
111111
✓ DO: Generate exactly 4 learning objectives (no more, no less)
112-
✓ DO: Keep each learning objective to 5 words or fewer
112+
✓ DO: Keep each learning objective to 5 words or fewer - THIS IS STRICTLY ENFORCED
113+
- Good: "Master active context engineering" (4 words) ✓
114+
- Bad: "Learn how to master active context" (6 words) ✗
113115
114116
✗ AVOID: Long paragraphs on slides (slides are visual anchors, not reading material)
115117
✗ AVOID: More than 5 bullet points per slide
@@ -626,11 +628,20 @@ STEP 2: Then, condense to the 3-5 MOST critical takeaways
626628
- Prioritize by impact and generality (what will matter most in production?)
627629
- Combine related points into higher-level insights when possible
628630
- Remove redundant or overly specific points
629-
- Ensure each takeaway is actionable and memorable
631+
- **STRICT REQUIREMENT: Each takeaway MUST be 5 words or fewer**
632+
- Use active verbs and eliminate filler words
633+
- Examples:
634+
✓ "Tests ground agent code quality" (5 words)
635+
✓ "Context management improves agent reliability" (5 words)
636+
✓ "Prompt versioning prevents regression bugs" (5 words)
637+
✗ "Tests are critical for agent workflows in production" (8 words)
638+
✗ "You should manage context to improve reliability" (7 words)
630639
631640
IMPORTANT: The final takeaway slide MUST have exactly 3-5 items, even if the source material lists more.
632641
Quality over quantity—choose the most impactful insights.
633642
643+
WORD COUNT VALIDATION: This is strictly enforced. The build will fail if any takeaway exceeds 5 words.
644+
634645
CRITICAL REQUIREMENTS:
635646
636647
1. The output MUST be valid JSON - no preamble, no explanation, just the JSON object
@@ -640,6 +651,8 @@ CRITICAL REQUIREMENTS:
640651
5. Code examples must be actual code from the lesson, not pseudocode
641652
6. Content arrays MUST have 3-5 items (except title slide) - THIS IS STRICTLY ENFORCED
642653
7. PROMPT EXAMPLES: Use "code" or "codeComparison" slide types, NEVER bullet points
654+
8. Learning objectives MUST be 5 words or fewer - THIS IS STRICTLY ENFORCED
655+
9. Takeaway items MUST be 5 words or fewer - THIS IS STRICTLY ENFORCED
643656
644657
BEFORE YOU GENERATE - CHECKLIST:
645658
@@ -1162,6 +1175,73 @@ function validateCodeExamplesExistInSource(content, presentation) {
11621175
};
11631176
}
11641177

1178+
/**
1179+
* Validate that takeaway items have 5 words or fewer
1180+
*
1181+
* Takeaways are final memorable insights displayed prominently on conclusion slides.
1182+
* Enforcing brevity ensures they're memorable and impactful for the audience.
1183+
*/
1184+
function validateTakeawayWordCount(presentation) {
1185+
const MAX_WORDS = 5;
1186+
const issues = [];
1187+
1188+
const takeawaySlides = presentation.slides.filter(s => s.type === 'takeaway');
1189+
1190+
for (const slide of takeawaySlides) {
1191+
if (slide.content && Array.isArray(slide.content)) {
1192+
slide.content.forEach((item, index) => {
1193+
const wordCount = item.trim().split(/\s+/).length;
1194+
if (wordCount > MAX_WORDS) {
1195+
issues.push({
1196+
slide: slide.title,
1197+
index: index + 1,
1198+
wordCount,
1199+
content: item.substring(0, 60) + (item.length > 60 ? '...' : ''),
1200+
excess: wordCount - MAX_WORDS
1201+
});
1202+
}
1203+
});
1204+
}
1205+
}
1206+
1207+
return {
1208+
valid: issues.length === 0,
1209+
issues,
1210+
totalTakeawaysChecked: takeawaySlides.reduce((sum, s) => sum + (s.content?.length || 0), 0)
1211+
};
1212+
}
1213+
1214+
/**
1215+
* Validate that learning objectives have 5 words or fewer
1216+
*
1217+
* Learning objectives appear on the title slide and set expectations for the lesson.
1218+
* Brief objectives are more memorable and easier for students to internalize.
1219+
*/
1220+
function validateLearningObjectivesWordCount(presentation) {
1221+
const MAX_WORDS = 5;
1222+
const issues = [];
1223+
1224+
const objectives = presentation.metadata?.learningObjectives || [];
1225+
1226+
objectives.forEach((objective, index) => {
1227+
const wordCount = objective.trim().split(/\s+/).length;
1228+
if (wordCount > MAX_WORDS) {
1229+
issues.push({
1230+
index: index + 1,
1231+
wordCount,
1232+
content: objective.substring(0, 60) + (objective.length > 60 ? '...' : ''),
1233+
excess: wordCount - MAX_WORDS
1234+
});
1235+
}
1236+
});
1237+
1238+
return {
1239+
valid: issues.length === 0,
1240+
issues,
1241+
totalObjectivesChecked: objectives.length
1242+
};
1243+
}
1244+
11651245
/**
11661246
* Generate presentation for a file
11671247
*/
@@ -1288,6 +1368,44 @@ async function generatePresentation(filePath, manifest, config) {
12881368
console.log(` ✅ All ${codeSourceValidation.codeSlidesChecked} code slide(s) verified against source`);
12891369
}
12901370

1371+
// Validate takeaway word count (5 words or fewer)
1372+
// CRITICAL: This validation is intentionally strict and throws an error because
1373+
// takeaways are displayed prominently on conclusion slides and must be memorable.
1374+
// Verbose takeaways defeat the purpose of distilling key insights.
1375+
const takeawayValidation = validateTakeawayWordCount(presentation);
1376+
if (!takeawayValidation.valid) {
1377+
console.log(` ❌ BUILD FAILURE: ${takeawayValidation.issues.length} takeaway word limit violation(s):`);
1378+
takeawayValidation.issues.forEach(issue => {
1379+
console.log(` - "${issue.slide}" item ${issue.index}: ${issue.wordCount} words (${issue.excess} over limit)`);
1380+
console.log(` "${issue.content}"`);
1381+
});
1382+
console.log(` ℹ️ All takeaway items MUST be 5 words or fewer for memorability`);
1383+
console.log(` ℹ️ Examples: "Tests ground agent code quality" (5) ✓ | "Tests are critical for agent workflows" (6) ✗`);
1384+
console.log(` ℹ️ The presentation was not saved. Fix the generation and try again.`);
1385+
throw new Error('Takeaway validation failed - items exceed 5-word limit');
1386+
} else if (takeawayValidation.totalTakeawaysChecked > 0) {
1387+
console.log(` ✅ All ${takeawayValidation.totalTakeawaysChecked} takeaway item(s) are 5 words or fewer`);
1388+
}
1389+
1390+
// Validate learning objectives word count (5 words or fewer)
1391+
// CRITICAL: This validation is intentionally strict and throws an error because
1392+
// learning objectives appear on the title slide and set learner expectations.
1393+
// Brief objectives are more memorable and easier to internalize.
1394+
const objectivesValidation = validateLearningObjectivesWordCount(presentation);
1395+
if (!objectivesValidation.valid) {
1396+
console.log(` ❌ BUILD FAILURE: ${objectivesValidation.issues.length} learning objective word limit violation(s):`);
1397+
objectivesValidation.issues.forEach(issue => {
1398+
console.log(` - Objective ${issue.index}: ${issue.wordCount} words (${issue.excess} over limit)`);
1399+
console.log(` "${issue.content}"`);
1400+
});
1401+
console.log(` ℹ️ All learning objectives MUST be 5 words or fewer for clarity`);
1402+
console.log(` ℹ️ Examples: "Master active context engineering" (4) ✓ | "Learn how to master active context" (6) ✗`);
1403+
console.log(` ℹ️ The presentation was not saved. Fix the generation and try again.`);
1404+
throw new Error('Learning objectives validation failed - items exceed 5-word limit');
1405+
} else if (objectivesValidation.totalObjectivesChecked > 0) {
1406+
console.log(` ✅ All ${objectivesValidation.totalObjectivesChecked} learning objective(s) are 5 words or fewer`);
1407+
}
1408+
12911409
// Apply deterministic line breaking (AFTER validation passes)
12921410
console.log(' 🔧 Applying line breaking...');
12931411
const { presentation: processedPresentation, stats } = processPresentation(presentation);

website/static/presentations/manifest.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@
5050
},
5151
"practical-techniques/lesson-8-tests-as-guardrails.md": {
5252
"presentationUrl": "/presentations/practical-techniques/lesson-8-tests-as-guardrails.json",
53-
"slideCount": 11,
53+
"slideCount": 12,
5454
"estimatedDuration": "40-50 minutes",
55-
"title": "Tests as Guardrails",
56-
"generatedAt": "2025-11-10T19:55:47.621Z"
55+
"title": "Lesson 8: Tests as Guardrails",
56+
"generatedAt": "2025-11-10T20:09:05.001Z"
5757
},
5858
"practical-techniques/lesson-9-reviewing-code.md": {
5959
"presentationUrl": "/presentations/practical-techniques/lesson-9-reviewing-code.json",

0 commit comments

Comments
 (0)