claude-presentation-master 6.1.0 → 7.2.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.
@@ -0,0 +1,446 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AUTOMATED PRESENTATION QUALITY ASSURANCE
4
+ *
5
+ * Iteratively scores and fixes presentations until ALL dimensions hit 95+
6
+ * Target: 100, Minimum: 95
7
+ *
8
+ * Usage: node auto-fix-presentation.cjs <html-file>
9
+ */
10
+
11
+ const { chromium } = require('playwright');
12
+ const path = require('path');
13
+ const fs = require('fs');
14
+
15
+ const TARGET_SCORE = 95;
16
+ const MAX_ITERATIONS = 10;
17
+
18
+ // CSS fixes for common issues
19
+ const CSS_FIXES = {
20
+ smallText: {
21
+ selector: '.small-text-fix',
22
+ css: 'font-size: 1.25rem !important;'
23
+ },
24
+ lowContrast: {
25
+ selector: '.contrast-fix',
26
+ css: 'color: #ffffff !important; text-shadow: 0 2px 10px rgba(0,0,0,0.8) !important;'
27
+ },
28
+ titleContrast: {
29
+ selector: '.title-contrast-fix',
30
+ css: 'color: #ffffff !important; text-shadow: 0 4px 20px rgba(0,0,0,0.9) !important;'
31
+ }
32
+ };
33
+
34
+ async function analyzeSlide(page) {
35
+ return await page.evaluate(() => {
36
+ const slide = document.querySelector('.present') || document.querySelector('section');
37
+ if (!slide) return { error: 'No slide found' };
38
+
39
+ function parseColor(colorStr) {
40
+ if (!colorStr) return null;
41
+ const match = colorStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
42
+ if (match) return { r: parseInt(match[1]), g: parseInt(match[2]), b: parseInt(match[3]) };
43
+ return null;
44
+ }
45
+
46
+ function getLuminance(rgb) {
47
+ const [r, g, b] = [rgb.r, rgb.g, rgb.b].map(c => {
48
+ c = c / 255;
49
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
50
+ });
51
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
52
+ }
53
+
54
+ function getContrast(fg, bg) {
55
+ if (!fg || !bg) return 21;
56
+ const l1 = getLuminance(fg);
57
+ const l2 = getLuminance(bg);
58
+ return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
59
+ }
60
+
61
+ function getEffectiveBg(element) {
62
+ let el = element;
63
+ while (el && el !== document.body) {
64
+ const bg = window.getComputedStyle(el).backgroundColor;
65
+ if (bg && bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent') {
66
+ const parsed = parseColor(bg);
67
+ if (parsed) return parsed;
68
+ }
69
+ el = el.parentElement;
70
+ }
71
+ return { r: 10, g: 10, b: 11 };
72
+ }
73
+
74
+ const textContent = slide.innerText || '';
75
+ const words = textContent.split(/\\s+/).filter(w => w.length > 0);
76
+ const wordCount = words.length;
77
+
78
+ const allText = slide.querySelectorAll('h1, h2, h3, h4, h5, h6, p, li, span, div');
79
+ const textIssues = [];
80
+
81
+ allText.forEach(el => {
82
+ const text = el.innerText?.trim();
83
+ if (!text || text.length < 2) return;
84
+
85
+ const style = window.getComputedStyle(el);
86
+ const fontSize = parseFloat(style.fontSize);
87
+ let color = parseColor(style.color);
88
+ if (!color) color = { r: 255, g: 255, b: 255 };
89
+ const bgColor = getEffectiveBg(el);
90
+ const contrast = getContrast(color, bgColor);
91
+ const isTitle = el.tagName.match(/^H[1-3]$/) || fontSize >= 30;
92
+
93
+ if (fontSize < 18) {
94
+ textIssues.push({ type: 'smallText', element: el.tagName, text: text.substring(0, 30), fontSize });
95
+ }
96
+ if (isTitle && contrast < 7) {
97
+ textIssues.push({ type: 'titleContrast', element: el.tagName, text: text.substring(0, 30), contrast });
98
+ } else if (!isTitle && contrast < 4.5) {
99
+ textIssues.push({ type: 'bodyContrast', element: el.tagName, text: text.substring(0, 30), contrast });
100
+ }
101
+ });
102
+
103
+ const images = slide.querySelectorAll('img');
104
+ const majorElements = slide.querySelectorAll('h1, h2, h3, img, .card, .stat, ul, ol');
105
+
106
+ return {
107
+ wordCount,
108
+ textIssues,
109
+ elementCount: majorElements.length,
110
+ imageCount: images.length,
111
+ hasTitle: !!slide.querySelector('h1, h2, h3')
112
+ };
113
+ });
114
+ }
115
+
116
+ function calculateScores(analysis) {
117
+ let layout = 100, contrast = 100, graphics = 100;
118
+ let content = 100, clarity = 100, effectiveness = 100;
119
+ const issues = [];
120
+
121
+ // Layout scoring
122
+ if (analysis.wordCount > 40) {
123
+ layout -= Math.min(30, (analysis.wordCount - 40) * 2);
124
+ issues.push(`Words: ${analysis.wordCount} (max 40)`);
125
+ }
126
+ if (analysis.elementCount > 7) {
127
+ layout -= 15;
128
+ issues.push(`Elements: ${analysis.elementCount} (max 7)`);
129
+ }
130
+ const smallTextCount = analysis.textIssues.filter(i => i.type === 'smallText').length;
131
+ if (smallTextCount > 0) {
132
+ layout -= smallTextCount * 5;
133
+ issues.push(`Small text: ${smallTextCount} elements`);
134
+ }
135
+
136
+ // Contrast scoring
137
+ const titleContrastIssues = analysis.textIssues.filter(i => i.type === 'titleContrast');
138
+ const bodyContrastIssues = analysis.textIssues.filter(i => i.type === 'bodyContrast');
139
+ if (titleContrastIssues.length > 0) {
140
+ contrast -= titleContrastIssues.length * 20;
141
+ issues.push(`Title contrast issues: ${titleContrastIssues.length}`);
142
+ }
143
+ if (bodyContrastIssues.length > 0) {
144
+ contrast -= bodyContrastIssues.length * 10;
145
+ issues.push(`Body contrast issues: ${bodyContrastIssues.length}`);
146
+ }
147
+
148
+ // Graphics scoring
149
+ if (analysis.imageCount === 0 && analysis.wordCount > 20) {
150
+ graphics -= 20;
151
+ issues.push('No images on text-heavy slide');
152
+ }
153
+
154
+ // Content scoring
155
+ if (analysis.wordCount < 10 && analysis.wordCount > 0) content -= 10;
156
+ if (!analysis.hasTitle) {
157
+ content -= 20;
158
+ issues.push('No title');
159
+ }
160
+
161
+ // Clarity scoring
162
+ if (analysis.wordCount > 50) clarity -= 20;
163
+ if (analysis.elementCount > 5) clarity -= (analysis.elementCount - 5) * 5;
164
+
165
+ // Effectiveness
166
+ if (!analysis.hasTitle) effectiveness -= 25;
167
+ if (titleContrastIssues.length > 0) effectiveness -= 30;
168
+
169
+ return {
170
+ layout: Math.max(0, Math.round(layout)),
171
+ contrast: Math.max(0, Math.round(contrast)),
172
+ graphics: Math.max(0, Math.round(graphics)),
173
+ content: Math.max(0, Math.round(content)),
174
+ clarity: Math.max(0, Math.round(clarity)),
175
+ effectiveness: Math.max(0, Math.round(effectiveness)),
176
+ consistency: 100,
177
+ issues
178
+ };
179
+ }
180
+
181
+ async function runFullAnalysis(htmlPath) {
182
+ const browser = await chromium.launch();
183
+ const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
184
+
185
+ await page.goto(`file://${path.resolve(htmlPath)}`, { waitUntil: 'networkidle' });
186
+ await page.waitForTimeout(3000);
187
+
188
+ const totalSlides = await page.evaluate(() => {
189
+ if (typeof Reveal !== 'undefined') return Reveal.getTotalSlides();
190
+ return document.querySelectorAll('.slides > section').length || 10;
191
+ });
192
+
193
+ const slideResults = [];
194
+
195
+ for (let i = 0; i < totalSlides; i++) {
196
+ const analysis = await analyzeSlide(page);
197
+ const scores = calculateScores(analysis);
198
+ const overall = Math.round(Object.values(scores).filter(v => typeof v === 'number').reduce((a, b) => a + b, 0) / 7);
199
+
200
+ slideResults.push({
201
+ slide: i + 1,
202
+ scores,
203
+ overall,
204
+ issues: scores.issues,
205
+ analysis
206
+ });
207
+
208
+ await page.keyboard.press('ArrowRight');
209
+ await page.waitForTimeout(1000);
210
+ }
211
+
212
+ await browser.close();
213
+
214
+ // Calculate dimension averages
215
+ const dimensions = ['layout', 'contrast', 'graphics', 'content', 'clarity', 'effectiveness', 'consistency'];
216
+ const averages = {};
217
+ dimensions.forEach(dim => {
218
+ averages[dim] = Math.round(slideResults.reduce((sum, r) => sum + r.scores[dim], 0) / totalSlides);
219
+ });
220
+ averages.total = Math.round(Object.values(averages).reduce((a, b) => a + b, 0) / dimensions.length);
221
+
222
+ return { slideResults, averages, totalSlides };
223
+ }
224
+
225
+ function printScores(averages, slideResults) {
226
+ console.log('\\n ═══════════════════════════════════════════════════');
227
+ console.log(' DIMENSION SCORES');
228
+ console.log(' ═══════════════════════════════════════════════════');
229
+ console.log(` Layout: ${averages.layout.toString().padStart(3)}/100 ${averages.layout >= TARGET_SCORE ? '✅' : '❌'}`);
230
+ console.log(` Contrast: ${averages.contrast.toString().padStart(3)}/100 ${averages.contrast >= TARGET_SCORE ? '✅' : '❌'}`);
231
+ console.log(` Graphics: ${averages.graphics.toString().padStart(3)}/100 ${averages.graphics >= TARGET_SCORE ? '✅' : '❌'}`);
232
+ console.log(` Content: ${averages.content.toString().padStart(3)}/100 ${averages.content >= TARGET_SCORE ? '✅' : '❌'}`);
233
+ console.log(` Clarity: ${averages.clarity.toString().padStart(3)}/100 ${averages.clarity >= TARGET_SCORE ? '✅' : '❌'}`);
234
+ console.log(` Effectiveness: ${averages.effectiveness.toString().padStart(3)}/100 ${averages.effectiveness >= TARGET_SCORE ? '✅' : '❌'}`);
235
+ console.log(` Consistency: ${averages.consistency.toString().padStart(3)}/100 ${averages.consistency >= TARGET_SCORE ? '✅' : '❌'}`);
236
+ console.log(' ───────────────────────────────────────────────────');
237
+ console.log(` TOTAL: ${averages.total.toString().padStart(3)}/100 ${averages.total >= TARGET_SCORE ? '✅' : '❌'}`);
238
+ console.log(' ═══════════════════════════════════════════════════\\n');
239
+
240
+ // Show failing slides
241
+ const failingSlides = slideResults.filter(r => r.overall < TARGET_SCORE);
242
+ if (failingSlides.length > 0) {
243
+ console.log(' SLIDES BELOW TARGET:');
244
+ failingSlides.forEach(r => {
245
+ console.log(` Slide ${r.slide}: ${r.overall}/100 - ${r.issues.join(', ')}`);
246
+ });
247
+ console.log('');
248
+ }
249
+ }
250
+
251
+ function identifyFixes(results) {
252
+ const fixes = [];
253
+
254
+ results.slideResults.forEach(r => {
255
+ if (r.scores.layout < TARGET_SCORE) {
256
+ const smallText = r.analysis.textIssues.filter(i => i.type === 'smallText');
257
+ if (smallText.length > 0) {
258
+ fixes.push({
259
+ slide: r.slide,
260
+ type: 'css',
261
+ target: 'small-fonts',
262
+ description: `Increase ${smallText.length} small font sizes`
263
+ });
264
+ }
265
+ if (r.analysis.wordCount > 40) {
266
+ fixes.push({
267
+ slide: r.slide,
268
+ type: 'content',
269
+ target: 'word-count',
270
+ description: `Reduce words from ${r.analysis.wordCount} to 40`
271
+ });
272
+ }
273
+ }
274
+
275
+ if (r.scores.contrast < TARGET_SCORE) {
276
+ const contrastIssues = r.analysis.textIssues.filter(i => i.type.includes('Contrast'));
277
+ if (contrastIssues.length > 0) {
278
+ fixes.push({
279
+ slide: r.slide,
280
+ type: 'css',
281
+ target: 'contrast',
282
+ description: `Fix ${contrastIssues.length} contrast issues`
283
+ });
284
+ }
285
+ }
286
+
287
+ if (r.scores.graphics < TARGET_SCORE && r.analysis.imageCount === 0) {
288
+ fixes.push({
289
+ slide: r.slide,
290
+ type: 'content',
291
+ target: 'add-image',
292
+ description: 'Add image to text-heavy slide'
293
+ });
294
+ }
295
+ });
296
+
297
+ return fixes;
298
+ }
299
+
300
+ async function applyFixes(htmlPath, fixes) {
301
+ let html = fs.readFileSync(htmlPath, 'utf8');
302
+ let modified = false;
303
+
304
+ // Apply CSS fixes for small fonts and contrast
305
+ const cssFixesToApply = fixes.filter(f => f.type === 'css');
306
+
307
+ if (cssFixesToApply.some(f => f.target === 'small-fonts')) {
308
+ // Increase all small font sizes
309
+ const smallFontPatterns = [
310
+ { pattern: /font-size:\s*0\.7[0-9]*rem/g, replacement: 'font-size: 1.125rem' },
311
+ { pattern: /font-size:\s*0\.8[0-9]*rem/g, replacement: 'font-size: 1.125rem' },
312
+ { pattern: /font-size:\s*0\.9[0-9]*rem/g, replacement: 'font-size: 1.125rem' },
313
+ { pattern: /font-size:\s*1rem([^0-9])/g, replacement: 'font-size: 1.125rem$1' },
314
+ { pattern: /font-size:\s*1\.0rem/g, replacement: 'font-size: 1.125rem' },
315
+ ];
316
+
317
+ smallFontPatterns.forEach(({ pattern, replacement }) => {
318
+ if (pattern.test(html)) {
319
+ html = html.replace(pattern, replacement);
320
+ modified = true;
321
+ }
322
+ });
323
+ }
324
+
325
+ if (cssFixesToApply.some(f => f.target === 'contrast')) {
326
+ // Enhance text shadows and colors for contrast
327
+ if (!html.includes('text-shadow: 0 4px 20px')) {
328
+ html = html.replace(
329
+ /\.large-text\s*\{([^}]*)\}/,
330
+ '.large-text { $1 text-shadow: 0 4px 20px rgba(0,0,0,0.9) !important; }'
331
+ );
332
+ modified = true;
333
+ }
334
+
335
+ // Ensure body text is light colored
336
+ html = html.replace(
337
+ /color:\s*var\(--gray\)/g,
338
+ 'color: #e0e0e0'
339
+ );
340
+ html = html.replace(
341
+ /color:\s*var\(--muted\)/g,
342
+ 'color: #cccccc'
343
+ );
344
+ modified = true;
345
+ }
346
+
347
+ if (modified) {
348
+ fs.writeFileSync(htmlPath, html);
349
+ return true;
350
+ }
351
+ return false;
352
+ }
353
+
354
+ async function main(htmlPath) {
355
+ console.log('\\n══════════════════════════════════════════════════════════════');
356
+ console.log(' AUTOMATED PRESENTATION QUALITY ASSURANCE');
357
+ console.log(' Target: ALL dimensions 95+ | Maximum iterations: ' + MAX_ITERATIONS);
358
+ console.log('══════════════════════════════════════════════════════════════\\n');
359
+
360
+ let iteration = 0;
361
+ let allPassing = false;
362
+
363
+ while (iteration < MAX_ITERATIONS && !allPassing) {
364
+ iteration++;
365
+ console.log(`\\n ─── ITERATION ${iteration} ───────────────────────────────────\\n`);
366
+
367
+ // Run analysis
368
+ console.log(' Analyzing presentation...');
369
+ const results = await runFullAnalysis(htmlPath);
370
+
371
+ // Print current scores
372
+ printScores(results.averages, results.slideResults);
373
+
374
+ // Check if all dimensions pass
375
+ const dimensions = ['layout', 'contrast', 'graphics', 'content', 'clarity', 'effectiveness', 'consistency'];
376
+ const failingDimensions = dimensions.filter(d => results.averages[d] < TARGET_SCORE);
377
+
378
+ if (failingDimensions.length === 0) {
379
+ allPassing = true;
380
+ console.log(' ✅ ALL DIMENSIONS AT 95+ - PRESENTATION READY!\\n');
381
+ break;
382
+ }
383
+
384
+ console.log(` Failing dimensions: ${failingDimensions.join(', ')}\\n`);
385
+
386
+ // Identify and apply fixes
387
+ const fixes = identifyFixes(results);
388
+ if (fixes.length === 0) {
389
+ console.log(' ⚠️ No automatic fixes available. Manual intervention required.\\n');
390
+ console.log(' REQUIRED MANUAL FIXES:');
391
+ failingDimensions.forEach(dim => {
392
+ console.log(` - ${dim}: Currently ${results.averages[dim]}/100, needs ${TARGET_SCORE}+`);
393
+ });
394
+ break;
395
+ }
396
+
397
+ console.log(` Applying ${fixes.length} fixes...`);
398
+ fixes.forEach(f => console.log(` - Slide ${f.slide}: ${f.description}`));
399
+
400
+ const applied = await applyFixes(htmlPath, fixes);
401
+ if (!applied) {
402
+ console.log(' ⚠️ Could not apply fixes automatically.\\n');
403
+ break;
404
+ }
405
+
406
+ console.log(' Fixes applied. Re-analyzing...\\n');
407
+ }
408
+
409
+ if (!allPassing && iteration >= MAX_ITERATIONS) {
410
+ console.log(` ❌ MAX ITERATIONS (${MAX_ITERATIONS}) REACHED - Manual review required\\n`);
411
+ }
412
+
413
+ // Final report
414
+ const finalResults = await runFullAnalysis(htmlPath);
415
+ console.log('\\n ═══════════════════════════════════════════════════');
416
+ console.log(' FINAL SCORES');
417
+ console.log(' ═══════════════════════════════════════════════════');
418
+ printScores(finalResults.averages, finalResults.slideResults);
419
+
420
+ // Save report
421
+ const reportPath = path.join(path.dirname(htmlPath), 'qa-final-report.json');
422
+ fs.writeFileSync(reportPath, JSON.stringify({
423
+ file: htmlPath,
424
+ timestamp: new Date().toISOString(),
425
+ iterations: iteration,
426
+ passed: allPassing,
427
+ finalScores: finalResults.averages,
428
+ slideDetails: finalResults.slideResults
429
+ }, null, 2));
430
+
431
+ console.log(` Report saved: ${reportPath}\\n`);
432
+
433
+ process.exit(allPassing ? 0 : 1);
434
+ }
435
+
436
+ // Run
437
+ const htmlFile = process.argv[2];
438
+ if (!htmlFile) {
439
+ console.error('Usage: node auto-fix-presentation.cjs <html-file>');
440
+ process.exit(1);
441
+ }
442
+
443
+ main(htmlFile).catch(err => {
444
+ console.error('Error:', err.message);
445
+ process.exit(1);
446
+ });
package/bin/cli.js CHANGED
@@ -20,10 +20,10 @@ const args = process.argv.slice(2);
20
20
 
21
21
  // Help text
22
22
  const helpText = `
23
- Claude Presentation Master v6.0.0
23
+ Claude Presentation Master v7.2.0
24
24
  Generate world-class presentations using expert methodologies
25
25
 
26
- 100% FREE • Zero API Keys • Runs Locally
26
+ 100% FREE • Zero API Keys • Runs Locally • KB-Driven Quality
27
27
 
28
28
  USAGE:
29
29
  npx claude-presentation-master <command> [options]
@@ -35,20 +35,23 @@ COMMANDS:
35
35
  info Show package information
36
36
 
37
37
  OPTIONS:
38
- -o, --output <dir> Output directory (default: ./output)
39
- -m, --mode <mode> Presentation mode: keynote or business (default: keynote)
40
- -f, --format <fmt> Output formats: html, pptx, or both (default: html)
41
- --type <type> Presentation type (auto-detected if omitted):
42
- ted_keynote, sales_pitch, consulting_deck,
43
- investment_banking, investor_pitch,
44
- technical_presentation, all_hands
45
- -t, --theme <name> Theme: default, consulting-classic, modern-tech, minimal
46
- --title <title> Presentation title (default: filename)
47
- --author <name> Author name
48
- --threshold <num> QA score threshold 0-100 (default: 95)
49
- --skip-qa Skip QA validation (NOT recommended)
50
- -h, --help Show this help message
51
- -v, --version Show version number
38
+ -o, --output <dir> Output directory (default: ./output)
39
+ -m, --mode <mode> Presentation mode: keynote or business (default: keynote)
40
+ -f, --format <fmt> Output formats: html, pptx, or both (default: html)
41
+ --type <type> Presentation type (auto-detected if omitted):
42
+ ted_keynote, sales_pitch, consulting_deck,
43
+ investment_banking, investor_pitch,
44
+ technical_presentation, all_hands
45
+ -t, --theme <name> Theme: default, consulting-classic, modern-tech, minimal
46
+ --title <title> Presentation title (default: filename)
47
+ --author <name> Author name
48
+ --min-score <num> Minimum QA score 0-100 (default: 95)
49
+ --threshold <num> Alias for --min-score
50
+ --max-iterations <num> Max auto-fix iterations (default: 5)
51
+ --no-iterative Disable iterative QA (single pass only)
52
+ --skip-qa Skip QA validation (NOT recommended)
53
+ -h, --help Show this help message
54
+ -v, --version Show version number
52
55
 
53
56
  EXAMPLES:
54
57
  # Quick one-off: Generate keynote from markdown (npx - no install needed)
@@ -85,7 +88,7 @@ For more information: https://github.com/Stuinfla/claude-presentation-master
85
88
  `;
86
89
 
87
90
  // Version
88
- const version = '6.0.0';
91
+ const version = '7.2.0';
89
92
 
90
93
  // Parse arguments
91
94
  function parseArgs(args) {
@@ -100,6 +103,8 @@ function parseArgs(args) {
100
103
  title: null,
101
104
  author: null,
102
105
  threshold: 95,
106
+ maxIterations: 5,
107
+ useIterativeQA: true,
103
108
  skipQA: false,
104
109
  help: false,
105
110
  version: false
@@ -153,9 +158,18 @@ function parseArgs(args) {
153
158
  break;
154
159
 
155
160
  case '--threshold':
161
+ case '--min-score':
156
162
  options.threshold = parseInt(args[++i], 10);
157
163
  break;
158
164
 
165
+ case '--max-iterations':
166
+ options.maxIterations = parseInt(args[++i], 10);
167
+ break;
168
+
169
+ case '--no-iterative':
170
+ options.useIterativeQA = false;
171
+ break;
172
+
159
173
  case '--skip-qa':
160
174
  options.skipQA = true;
161
175
  break;
@@ -221,15 +235,30 @@ Author: Stuart Kerr <stuart@isovision.ai>
221
235
  License: MIT
222
236
  Cost: 100% FREE - Zero API Keys Required
223
237
 
238
+ NEW IN v7.0.0:
239
+ ★ 7-Dimension Quality Scoring (KB-driven)
240
+ ★ Iterative Auto-Fix Loop (generates → scores → fixes → repeats)
241
+ ★ Knowledge Base Integration (73K+ tokens of expert rules)
242
+ ★ Per-dimension breakdown: Layout, Contrast, Graphics, Content, Clarity, Effectiveness, Consistency
243
+
224
244
  Features:
225
245
  ✓ 7 Presentation Types with Expert Methodologies
226
246
  ✓ Two modes: Keynote (6-25 words) & Business (40-80 words)
227
247
  ✓ HTML (Reveal.js) + PowerPoint output
228
248
  ✓ Investment Banking Charts (Football Field, Waterfall, Comps)
229
249
  ✓ Professional Typography (Google Fonts)
230
- Hallucination Detection (zero-tolerance fact checking)
231
- ✓ Auto-Remediation (fixes issues until 95/100 threshold met)
232
- Real visual QA with Playwright (optional)
250
+ KB-Driven Slide Generation (all decisions from knowledge base)
251
+ ✓ Auto-Remediation (fixes issues until threshold met)
252
+ 7-Dimension Scoring with Iteration Loop
253
+
254
+ 7-Dimension Scoring:
255
+ • Layout (15%) - Whitespace, balance, structure
256
+ • Contrast (15%) - WCAG compliance, readability
257
+ • Graphics (10%) - Images, placement, relevance
258
+ • Content (20%) - Word limits, bullets, structure
259
+ • Clarity (15%) - Message focus, density
260
+ • Effectiveness(15%) - Expert methodology compliance
261
+ • Consistency (10%) - Style uniformity, coherence
233
262
 
234
263
  Presentation Types:
235
264
  • ted_keynote Nancy Duarte (Sparkline)
@@ -241,14 +270,16 @@ Presentation Types:
241
270
  • all_hands Internal Comms Best Practices
242
271
 
243
272
  Expert Knowledge Base:
244
- 6,300+ lines of expert principles
273
+ 73K+ tokens of comprehensive rules
245
274
  • 40+ presentation experts
246
275
  • SCQA, Sparkline, STAR Moment frameworks
276
+ • Technical presentation templates (ADR, Post-mortem, SDR)
247
277
  • Automated type detection and framework selection
248
278
 
249
279
  Usage:
250
280
  npx claude-presentation-master generate notes.md -m keynote
251
281
  cpm generate strategy.md --type consulting_deck -f pptx
282
+ cpm generate content.md --min-score 98 --max-iterations 10
252
283
  `);
253
284
  process.exit(0);
254
285
  }
@@ -369,6 +400,8 @@ async function runGenerate(inputPath, options, generate) {
369
400
  title,
370
401
  author: options.author,
371
402
  qaThreshold: options.threshold,
403
+ maxIterations: options.maxIterations,
404
+ useIterativeQA: options.useIterativeQA,
372
405
  skipQA: options.skipQA,
373
406
  images: localImages, // Local images for backgrounds
374
407
  imageBasePath: inputDir // Base path for resolving relative image paths
@@ -376,13 +409,15 @@ async function runGenerate(inputPath, options, generate) {
376
409
 
377
410
  console.log(`
378
411
  📋 Configuration:
379
- Mode: ${options.mode}
380
- Type: ${options.type || '(auto-detect)'}
381
- Format: ${options.format.join(', ')}
382
- Theme: ${options.theme}
383
- Title: ${title}
384
- Threshold: ${options.threshold}/100
385
- Skip QA: ${options.skipQA ? 'Yes (NOT RECOMMENDED)' : 'No'}
412
+ Mode: ${options.mode}
413
+ Type: ${options.type || '(auto-detect)'}
414
+ Format: ${options.format.join(', ')}
415
+ Theme: ${options.theme}
416
+ Title: ${title}
417
+ Min Score: ${options.threshold}/100
418
+ Max Iter: ${options.maxIterations}
419
+ Iterative QA: ${options.useIterativeQA ? 'Yes (7-Dimension)' : 'No (Single Pass)'}
420
+ Skip QA: ${options.skipQA ? 'Yes (NOT RECOMMENDED)' : 'No'}
386
421
  `);
387
422
 
388
423
  try {
@@ -412,21 +447,49 @@ async function runGenerate(inputPath, options, generate) {
412
447
 
413
448
  // Show results
414
449
  const avgWords = result.metadata.avgWordsPerSlide?.toFixed(1) ?? 'N/A';
415
- const estimatedDuration = Math.ceil((result.metadata.totalWords || 0) / 150); // ~150 words per minute
450
+ const totalWords = result.metadata.wordCount || result.metadata.totalWords || 0;
451
+ const estimatedDuration = Math.ceil(totalWords / 150); // ~150 words per minute
416
452
 
417
453
  console.log(`
418
454
  ╔════════════════════════════════════════════════════════╗
419
455
  ║ QA RESULTS ║
420
456
  ╚════════════════════════════════════════════════════════╝
421
457
 
422
- 📊 Score: ${result.score}/100 ${result.score >= options.threshold ? '✅ PASSED' : '❌ FAILED'}
458
+ 📊 Overall Score: ${result.score}/100 ${result.score >= options.threshold ? '✅ PASSED' : '❌ FAILED'}
459
+ `);
423
460
 
461
+ // Show 7-dimension breakdown if available
462
+ if (result.qaResults.dimensions) {
463
+ const dims = result.qaResults.dimensions;
464
+ console.log('7-Dimension Breakdown:');
465
+ console.log('─────────────────────────────────────────────────────────');
466
+ const formatScore = (name, score) => {
467
+ const bar = '█'.repeat(Math.floor(score / 10)) + '░'.repeat(10 - Math.floor(score / 10));
468
+ const status = score >= 95 ? '✅' : score >= 80 ? '⚠️' : '❌';
469
+ return `${status} ${name.padEnd(14)} ${bar} ${score.toString().padStart(3)}/100`;
470
+ };
471
+ console.log(` ${formatScore('Layout', dims.layout)}`);
472
+ console.log(` ${formatScore('Contrast', dims.contrast)}`);
473
+ console.log(` ${formatScore('Graphics', dims.graphics)}`);
474
+ console.log(` ${formatScore('Content', dims.content)}`);
475
+ console.log(` ${formatScore('Clarity', dims.clarity)}`);
476
+ console.log(` ${formatScore('Effectiveness', dims.effectiveness)}`);
477
+ console.log(` ${formatScore('Consistency', dims.consistency)}`);
478
+ console.log('');
479
+ }
480
+
481
+ // Show iteration info if available
482
+ if (result.metadata.qaIterations) {
483
+ console.log(`🔄 Iterations: ${result.metadata.qaIterations}/${result.metadata.qaMaxIterations}`);
484
+ }
485
+
486
+ console.log(`
424
487
  📈 Metadata:
425
488
  Slides: ${result.metadata.slideCount}
426
- Words: ${result.metadata.totalWords || 'N/A'}
489
+ Words: ${totalWords}
427
490
  Avg/Slide: ${avgWords}
428
491
  Duration: ~${estimatedDuration} minutes
429
- Type: ${result.metadata.presentationType}
492
+ Type: ${result.metadata.presentationType || 'auto-detected'}
430
493
  `);
431
494
 
432
495
  console.log('\n✨ Generation complete!');