claude-presentation-master 5.0.0 → 6.0.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/index.mjs CHANGED
@@ -26,8 +26,280 @@ var TemplateNotFoundError = class extends Error {
26
26
  }
27
27
  };
28
28
 
29
+ // src/kb/KnowledgeGateway.ts
30
+ import { readFileSync } from "fs";
31
+ import { join, dirname } from "path";
32
+ import { fileURLToPath } from "url";
33
+ import * as yaml from "yaml";
34
+ function getModuleDir() {
35
+ if (typeof import.meta !== "undefined" && import.meta.url) {
36
+ return dirname(fileURLToPath(import.meta.url));
37
+ }
38
+ if (typeof __dirname !== "undefined") {
39
+ return __dirname;
40
+ }
41
+ return process.cwd();
42
+ }
43
+ var KnowledgeGateway = class {
44
+ kb;
45
+ loaded = false;
46
+ constructor() {
47
+ }
48
+ /**
49
+ * Load the knowledge base from YAML file
50
+ */
51
+ async load() {
52
+ if (this.loaded) return;
53
+ const moduleDir = getModuleDir();
54
+ const possiblePaths = [
55
+ join(moduleDir, "../../assets/presentation-knowledge.yaml"),
56
+ join(moduleDir, "../assets/presentation-knowledge.yaml"),
57
+ join(moduleDir, "../../../assets/presentation-knowledge.yaml"),
58
+ join(moduleDir, "assets/presentation-knowledge.yaml"),
59
+ join(process.cwd(), "assets/presentation-knowledge.yaml"),
60
+ join(process.cwd(), "node_modules/claude-presentation-master/assets/presentation-knowledge.yaml")
61
+ ];
62
+ for (const path of possiblePaths) {
63
+ try {
64
+ const content = readFileSync(path, "utf-8");
65
+ this.kb = yaml.parse(content);
66
+ this.loaded = true;
67
+ console.log(` \u2713 Knowledge base loaded from ${path}`);
68
+ return;
69
+ } catch {
70
+ }
71
+ }
72
+ throw new Error(`Knowledge base not found. Tried: ${possiblePaths.join(", ")}`);
73
+ }
74
+ /**
75
+ * Get presentation mode configuration (keynote or business)
76
+ */
77
+ getMode(mode) {
78
+ this.ensureLoaded();
79
+ return this.kb.presentation_modes[mode];
80
+ }
81
+ /**
82
+ * Determine which mode is best for a given presentation type
83
+ */
84
+ getModeForType(type) {
85
+ const keynoteTypes = ["ted_keynote", "sales_pitch", "all_hands"];
86
+ const businessTypes = ["consulting_deck", "investment_banking", "investor_pitch", "technical_presentation"];
87
+ if (keynoteTypes.includes(type)) return "keynote";
88
+ if (businessTypes.includes(type)) return "business";
89
+ return "keynote";
90
+ }
91
+ /**
92
+ * Get word limits for the specified mode
93
+ */
94
+ getWordLimits(mode) {
95
+ const modeConfig = this.getMode(mode);
96
+ const range = modeConfig.characteristics.words_per_slide;
97
+ if (mode === "keynote") {
98
+ return { min: 6, max: 15, ideal: 10 };
99
+ } else {
100
+ return { min: 40, max: 80, ideal: 60 };
101
+ }
102
+ }
103
+ /**
104
+ * Get bullet point limits
105
+ */
106
+ getBulletLimits(mode) {
107
+ if (mode === "keynote") {
108
+ return { maxBullets: 3, maxWordsPerBullet: 8 };
109
+ } else {
110
+ return { maxBullets: 5, maxWordsPerBullet: 20 };
111
+ }
112
+ }
113
+ /**
114
+ * Get whitespace percentage requirement
115
+ */
116
+ getWhitespaceRequirement(mode) {
117
+ if (mode === "keynote") return 0.4;
118
+ return 0.25;
119
+ }
120
+ /**
121
+ * Get slide templates for the specified mode
122
+ */
123
+ getSlideTemplates(mode) {
124
+ this.ensureLoaded();
125
+ const key = mode === "keynote" ? "keynote_mode" : "business_mode";
126
+ return this.kb.slide_templates[key] || [];
127
+ }
128
+ /**
129
+ * Get a specific slide template by name
130
+ */
131
+ getSlideTemplate(mode, templateName) {
132
+ const templates = this.getSlideTemplates(mode);
133
+ return templates.find((t) => t.name.toLowerCase().includes(templateName.toLowerCase()));
134
+ }
135
+ /**
136
+ * Get quality metrics for grading
137
+ */
138
+ getQualityMetrics() {
139
+ this.ensureLoaded();
140
+ return this.kb.quality_metrics;
141
+ }
142
+ /**
143
+ * Get anti-patterns to avoid
144
+ */
145
+ getAntiPatterns(mode) {
146
+ this.ensureLoaded();
147
+ const key = mode === "keynote" ? "keynote_mode" : "business_mode";
148
+ return this.kb.anti_patterns[key] || [];
149
+ }
150
+ /**
151
+ * Get data visualization anti-patterns
152
+ */
153
+ getDataVizAntiPatterns() {
154
+ this.ensureLoaded();
155
+ return this.kb.anti_patterns.data_visualization || [];
156
+ }
157
+ /**
158
+ * Get accessibility anti-patterns
159
+ */
160
+ getAccessibilityAntiPatterns() {
161
+ this.ensureLoaded();
162
+ return this.kb.anti_patterns.accessibility || [];
163
+ }
164
+ /**
165
+ * Get color palette by name
166
+ */
167
+ getColorPalette(name) {
168
+ this.ensureLoaded();
169
+ const palettes = this.kb.design_system.color_palettes;
170
+ return palettes[name];
171
+ }
172
+ /**
173
+ * Get recommended color palette for presentation type
174
+ */
175
+ getRecommendedPalette(type) {
176
+ this.ensureLoaded();
177
+ const palettes = this.kb.design_system.color_palettes;
178
+ switch (type) {
179
+ case "consulting_deck":
180
+ return palettes.consulting_classic;
181
+ case "investment_banking":
182
+ case "investor_pitch":
183
+ return palettes.executive_professional;
184
+ case "ted_keynote":
185
+ return palettes.dark_executive;
186
+ case "sales_pitch":
187
+ return palettes.modern_business;
188
+ case "technical_presentation":
189
+ return palettes.modern_business;
190
+ case "all_hands":
191
+ return palettes.strategy_growth;
192
+ default:
193
+ return palettes.consulting_classic;
194
+ }
195
+ }
196
+ /**
197
+ * Get typography guidelines for mode
198
+ */
199
+ getTypography(mode) {
200
+ this.ensureLoaded();
201
+ const key = mode === "keynote" ? "keynote_mode" : "business_mode";
202
+ return this.kb.design_system.typography[key];
203
+ }
204
+ /**
205
+ * Get story framework by name
206
+ */
207
+ getStoryFramework(name) {
208
+ this.ensureLoaded();
209
+ return this.kb.story_frameworks[name];
210
+ }
211
+ /**
212
+ * Get expert principles by name
213
+ */
214
+ getExpertPrinciples(expertName) {
215
+ this.ensureLoaded();
216
+ if (this.kb.experts[expertName]) {
217
+ return this.kb.experts[expertName];
218
+ }
219
+ if (this.kb.data_visualization_experts) {
220
+ for (const key of Object.keys(this.kb.data_visualization_experts)) {
221
+ if (key.includes(expertName.toLowerCase())) {
222
+ return this.kb.data_visualization_experts[key];
223
+ }
224
+ }
225
+ }
226
+ return null;
227
+ }
228
+ /**
229
+ * Get Duarte's glance test requirements
230
+ */
231
+ getDuarteGlanceTest() {
232
+ this.ensureLoaded();
233
+ const duarte = this.kb.experts.nancy_duarte;
234
+ return {
235
+ wordLimit: duarte.core_principles.glance_test.word_limit || 25,
236
+ timeSeconds: 3
237
+ };
238
+ }
239
+ /**
240
+ * Get Miller's Law constraints
241
+ */
242
+ getMillersLaw() {
243
+ this.ensureLoaded();
244
+ return { minItems: 5, maxItems: 9 };
245
+ }
246
+ /**
247
+ * Get cognitive load constraints
248
+ */
249
+ getCognitiveLoadLimits() {
250
+ this.ensureLoaded();
251
+ return { maxItemsPerChunk: 7 };
252
+ }
253
+ /**
254
+ * Get chart selection guidance
255
+ */
256
+ getChartGuidance(purpose) {
257
+ this.ensureLoaded();
258
+ return this.kb.chart_selection_guide.by_purpose[purpose];
259
+ }
260
+ /**
261
+ * Get charts to avoid
262
+ */
263
+ getChartsToAvoid() {
264
+ this.ensureLoaded();
265
+ const knaflic = this.kb.data_visualization_experts.cole_nussbaumer_knaflic;
266
+ return knaflic.chart_guidance.avoid_these || [];
267
+ }
268
+ /**
269
+ * Get investment banking pitch book structure
270
+ */
271
+ getIBPitchBookStructure(type) {
272
+ this.ensureLoaded();
273
+ return this.kb.investment_banking?.pitch_book_types[type];
274
+ }
275
+ /**
276
+ * Check if knowledge base is loaded
277
+ */
278
+ ensureLoaded() {
279
+ if (!this.loaded) {
280
+ throw new Error("Knowledge base not loaded. Call load() first.");
281
+ }
282
+ }
283
+ /**
284
+ * Get the full knowledge base (for advanced queries)
285
+ */
286
+ getRawKB() {
287
+ this.ensureLoaded();
288
+ return this.kb;
289
+ }
290
+ };
291
+ var gatewayInstance = null;
292
+ async function getKnowledgeGateway() {
293
+ if (!gatewayInstance) {
294
+ gatewayInstance = new KnowledgeGateway();
295
+ await gatewayInstance.load();
296
+ }
297
+ return gatewayInstance;
298
+ }
299
+
29
300
  // src/core/ContentAnalyzer.ts
30
301
  var ContentAnalyzer = class {
302
+ kb;
31
303
  // Signal words for SCQA detection
32
304
  situationSignals = [
33
305
  "currently",
@@ -39,7 +311,8 @@ var ContentAnalyzer = class {
39
311
  "our",
40
312
  "the market",
41
313
  "industry",
42
- "context"
314
+ "context",
315
+ "background"
43
316
  ];
44
317
  complicationSignals = [
45
318
  "however",
@@ -57,20 +330,10 @@ var ContentAnalyzer = class {
57
330
  "yet",
58
331
  "although",
59
332
  "despite",
60
- "while"
61
- ];
62
- questionSignals = [
63
- "how",
64
- "what",
65
- "why",
66
- "when",
67
- "where",
68
- "which",
69
- "should",
70
- "could",
71
- "can we",
72
- "is it possible",
73
- "?"
333
+ "while",
334
+ "crisis",
335
+ "gap",
336
+ "broken"
74
337
  ];
75
338
  answerSignals = [
76
339
  "therefore",
@@ -86,429 +349,439 @@ var ContentAnalyzer = class {
86
349
  "we should",
87
350
  "must",
88
351
  "need to",
89
- "the answer"
352
+ "the answer",
353
+ "introducing",
354
+ "presenting",
355
+ "platform"
90
356
  ];
91
- // Sparkline detection
92
- whatIsSignals = [
93
- "currently",
94
- "today",
95
- "status quo",
96
- "reality",
97
- "actual",
98
- "now",
99
- "existing",
100
- "present state",
101
- "as-is",
102
- "problem"
103
- ];
104
- whatCouldBeSignals = [
105
- "imagine",
106
- "vision",
107
- "future",
108
- "could be",
109
- "what if",
110
- "possibility",
111
- "potential",
112
- "opportunity",
113
- "transform",
114
- "envision",
115
- "ideal",
116
- "dream",
117
- "goal",
118
- "aspiration"
357
+ // CTA signals - used to filter these OUT of answer detection
358
+ ctaSignals = [
359
+ "contact",
360
+ "call us",
361
+ "email",
362
+ "schedule",
363
+ "sign up",
364
+ "register",
365
+ "next steps",
366
+ "get started",
367
+ "reach out",
368
+ "visit",
369
+ "follow"
119
370
  ];
371
+ // Type detection signals
372
+ typeSignals = {
373
+ ted_keynote: ["inspire", "vision", "imagine", "dream", "transform", "story", "journey"],
374
+ sales_pitch: ["buy", "purchase", "pricing", "offer", "deal", "discount", "roi", "value"],
375
+ consulting_deck: ["recommend", "analysis", "assessment", "strategy", "findings", "options", "mece"],
376
+ investment_banking: ["valuation", "dcf", "multiple", "enterprise value", "ebitda", "ipo", "m&a"],
377
+ investor_pitch: ["investment", "funding", "series", "traction", "market size", "tam", "runway"],
378
+ technical_presentation: ["architecture", "api", "system", "implementation", "code", "database", "deploy"],
379
+ all_hands: ["team", "quarter", "update", "progress", "celebrate", "recognize", "company"]
380
+ };
381
+ async initialize() {
382
+ this.kb = await getKnowledgeGateway();
383
+ }
120
384
  /**
121
385
  * Analyze content and extract structural elements.
122
386
  */
123
387
  async analyze(content, contentType) {
388
+ await this.initialize();
124
389
  const text = this.parseContent(content, contentType);
125
- const paragraphs = this.splitIntoParagraphs(text);
126
- const sentences = this.splitIntoSentences(text);
127
- const scqa = this.extractSCQA(paragraphs, sentences);
128
- const sparkline = this.extractSparkline(paragraphs);
129
- const keyMessages = this.extractKeyMessages(text, sentences);
130
- const titles = this.generateActionTitles(keyMessages, paragraphs);
131
- const starMoments = this.identifyStarMoments(paragraphs);
132
- const estimatedSlideCount = this.estimateSlideCount(text, paragraphs);
390
+ const title = this.extractTitle(text);
391
+ const detectedType = this.detectPresentationType(text);
392
+ console.log(` \u2713 Detected type: ${detectedType}`);
393
+ const sections = this.extractSections(text);
394
+ console.log(` \u2713 Found ${sections.length} sections`);
395
+ const scqa = this.extractSCQA(text);
396
+ const sparkline = this.extractSparkline(text);
397
+ const keyMessages = this.extractKeyMessages(text);
398
+ const dataPoints = this.extractDataPoints(text);
399
+ console.log(` \u2713 Found ${dataPoints.length} data points`);
400
+ const titles = sections.map((s) => s.header).filter((h) => h.length > 0);
401
+ const starMoments = this.extractStarMoments(text);
402
+ const estimatedSlideCount = Math.max(5, sections.length + 3);
133
403
  return {
134
- scqa,
135
- sparkline,
404
+ detectedType,
405
+ title,
406
+ sections,
136
407
  keyMessages,
408
+ dataPoints,
409
+ scqa: {
410
+ situation: scqa.situation || "",
411
+ complication: scqa.complication || "",
412
+ question: scqa.question || "",
413
+ answer: scqa.answer || ""
414
+ },
415
+ sparkline: {
416
+ whatIs: sparkline.callToAction ? [sparkline.callToAction] : [],
417
+ whatCouldBe: keyMessages,
418
+ callToAdventure: sparkline.callToAction || ""
419
+ },
137
420
  titles,
138
421
  starMoments,
139
422
  estimatedSlideCount
140
423
  };
141
424
  }
142
425
  /**
143
- * Parse content based on its type.
426
+ * Parse content based on type
144
427
  */
145
428
  parseContent(content, contentType) {
146
- switch (contentType) {
147
- case "markdown":
148
- return this.parseMarkdown(content);
149
- case "json":
150
- return this.parseJSON(content);
151
- case "yaml":
152
- return this.parseYAML(content);
153
- case "text":
154
- default:
155
- return content;
429
+ if (contentType === "markdown" || content.includes("#") || content.includes("- ")) {
430
+ return content;
156
431
  }
432
+ return content;
157
433
  }
158
434
  /**
159
- * Parse markdown content to plain text (preserving structure hints).
160
- */
161
- parseMarkdown(content) {
162
- let text = content;
163
- text = text.replace(/^#{1,6}\s+(.+)$/gm, "\n[HEADER] $1\n");
164
- text = text.replace(/^[-*+]\s+(.+)$/gm, "[BULLET] $1");
165
- text = text.replace(/^\d+\.\s+(.+)$/gm, "[NUMBERED] $1");
166
- text = text.replace(/\*\*(.+?)\*\*/g, "[EMPHASIS] $1 [/EMPHASIS]");
167
- text = text.replace(/\*(.+?)\*/g, "$1");
168
- text = text.replace(/```[\s\S]*?```/g, "[CODE BLOCK]");
169
- text = text.replace(/`(.+?)`/g, "$1");
170
- text = text.replace(/\[(.+?)\]\(.+?\)/g, "$1");
171
- text = text.replace(/!\[.*?\]\(.+?\)/g, "[IMAGE]");
172
- return text.trim();
173
- }
174
- /**
175
- * Parse JSON content.
435
+ * Extract the main title from content
176
436
  */
177
- parseJSON(content) {
178
- try {
179
- const data = JSON.parse(content);
180
- return this.flattenObject(data);
181
- } catch {
182
- return content;
437
+ extractTitle(text) {
438
+ const h1Match = text.match(/^#\s+(.+)$/m);
439
+ if (h1Match && h1Match[1]) {
440
+ return h1Match[1].replace(/\*\*/g, "").trim();
441
+ }
442
+ const lines = text.split("\n").filter((l) => l.trim().length > 0);
443
+ if (lines.length > 0 && lines[0]) {
444
+ return lines[0].replace(/^#+\s*/, "").replace(/\*\*/g, "").trim().slice(0, 80);
183
445
  }
446
+ return "Presentation";
184
447
  }
185
448
  /**
186
- * Parse YAML content.
449
+ * Detect presentation type from content signals
187
450
  */
188
- parseYAML(content) {
189
- const lines = content.split("\n");
190
- const values = [];
191
- for (const line of lines) {
192
- const match = line.match(/^[\s-]*(?:\w+:\s*)?(.+)$/);
193
- if (match?.[1] && !match[1].includes(":")) {
194
- values.push(match[1].trim());
451
+ detectPresentationType(text) {
452
+ const lowerText = text.toLowerCase();
453
+ const scores = {
454
+ ted_keynote: 0,
455
+ sales_pitch: 0,
456
+ consulting_deck: 0,
457
+ investment_banking: 0,
458
+ investor_pitch: 0,
459
+ technical_presentation: 0,
460
+ all_hands: 0
461
+ };
462
+ for (const [type, signals] of Object.entries(this.typeSignals)) {
463
+ for (const signal of signals) {
464
+ if (lowerText.includes(signal)) {
465
+ scores[type]++;
466
+ }
467
+ }
468
+ }
469
+ let maxScore = 0;
470
+ let detectedType = "consulting_deck";
471
+ for (const [type, score] of Object.entries(scores)) {
472
+ if (score > maxScore) {
473
+ maxScore = score;
474
+ detectedType = type;
195
475
  }
196
476
  }
197
- return values.join("\n");
477
+ return detectedType;
198
478
  }
199
479
  /**
200
- * Flatten object to text.
480
+ * Extract sections from content (headers, bullets, content)
201
481
  */
202
- flattenObject(obj, prefix = "") {
203
- const parts = [];
204
- if (typeof obj === "string") {
205
- return obj;
206
- }
207
- if (Array.isArray(obj)) {
208
- for (const item of obj) {
209
- parts.push(this.flattenObject(item, prefix));
482
+ extractSections(text) {
483
+ const sections = [];
484
+ const lines = text.split("\n");
485
+ let currentSection = null;
486
+ let contentLines = [];
487
+ for (const line of lines) {
488
+ const trimmedLine = line.trim();
489
+ const headerMatch = trimmedLine.match(/^(#{1,6})\s+(.+)$/);
490
+ if (headerMatch) {
491
+ if (currentSection) {
492
+ currentSection.content = contentLines.join("\n").trim();
493
+ if (currentSection.header || currentSection.content || currentSection.bullets.length > 0) {
494
+ sections.push(currentSection);
495
+ }
496
+ }
497
+ currentSection = {
498
+ header: (headerMatch[2] || "").replace(/\*\*/g, "").trim(),
499
+ level: (headerMatch[1] || "").length,
500
+ content: "",
501
+ bullets: [],
502
+ metrics: []
503
+ };
504
+ contentLines = [];
505
+ continue;
506
+ }
507
+ const bulletMatch = trimmedLine.match(/^[-*+]\s+(.+)$/);
508
+ if (bulletMatch && bulletMatch[1] && currentSection) {
509
+ currentSection.bullets.push(bulletMatch[1]);
510
+ continue;
511
+ }
512
+ const numberedMatch = trimmedLine.match(/^\d+\.\s+(.+)$/);
513
+ if (numberedMatch && numberedMatch[1] && currentSection) {
514
+ currentSection.bullets.push(numberedMatch[1]);
515
+ continue;
516
+ }
517
+ const metricMatch = trimmedLine.match(/\$?([\d,]+\.?\d*)[%]?\s*[-–—:]\s*(.+)/);
518
+ if (metricMatch && metricMatch[1] && metricMatch[2] && currentSection) {
519
+ currentSection.metrics.push({
520
+ value: metricMatch[1],
521
+ label: metricMatch[2].slice(0, 50)
522
+ });
523
+ continue;
524
+ }
525
+ if (trimmedLine.includes("|") && currentSection) {
526
+ const cells = trimmedLine.split("|").map((c) => c.trim()).filter((c) => c && !c.match(/^-+$/));
527
+ if (cells.length >= 2) {
528
+ const value = cells.find((c) => c.match(/^\$?[\d,]+\.?\d*[%]?$/));
529
+ const label = cells.find((c) => !c.match(/^\$?[\d,]+\.?\d*[%]?$/) && c.length > 2);
530
+ if (value && label) {
531
+ currentSection.metrics.push({ value, label: label.slice(0, 50) });
532
+ }
533
+ }
534
+ continue;
210
535
  }
211
- } else if (typeof obj === "object" && obj !== null) {
212
- for (const [key, value] of Object.entries(obj)) {
213
- const newPrefix = prefix ? `${prefix}.${key}` : key;
214
- parts.push(this.flattenObject(value, newPrefix));
536
+ if (trimmedLine && currentSection) {
537
+ contentLines.push(trimmedLine);
215
538
  }
216
- } else if (obj !== null && obj !== void 0) {
217
- parts.push(String(obj));
218
539
  }
219
- return parts.join("\n");
220
- }
221
- /**
222
- * Split text into paragraphs.
223
- */
224
- splitIntoParagraphs(text) {
225
- return text.split(/\n\n+/).map((p) => p.trim()).filter((p) => p.length > 0);
226
- }
227
- /**
228
- * Split text into sentences.
229
- */
230
- splitIntoSentences(text) {
231
- const cleaned = text.replace(/Mr\./g, "Mr").replace(/Mrs\./g, "Mrs").replace(/Dr\./g, "Dr").replace(/vs\./g, "vs").replace(/etc\./g, "etc").replace(/e\.g\./g, "eg").replace(/i\.e\./g, "ie");
232
- return cleaned.split(/[.!?]+/).map((s) => s.trim()).filter((s) => s.length > 10);
540
+ if (currentSection) {
541
+ currentSection.content = contentLines.join("\n").trim();
542
+ if (currentSection.header || currentSection.content || currentSection.bullets.length > 0) {
543
+ sections.push(currentSection);
544
+ }
545
+ }
546
+ return sections;
233
547
  }
234
548
  /**
235
- * Extract SCQA structure (Barbara Minto's Pyramid Principle).
549
+ * Extract SCQA structure (Barbara Minto)
236
550
  */
237
- extractSCQA(paragraphs, sentences) {
551
+ extractSCQA(text) {
552
+ const paragraphs = text.split(/\n\n+/).filter((p) => p.trim());
238
553
  let situation = "";
239
554
  let complication = "";
240
555
  let question = "";
241
556
  let answer = "";
242
557
  for (const para of paragraphs.slice(0, 3)) {
243
- if (this.containsSignals(para, this.situationSignals)) {
244
- situation = this.extractRelevantSentence(para, this.situationSignals);
558
+ if (this.containsSignals(para.toLowerCase(), this.situationSignals)) {
559
+ situation = this.extractFirstSentence(para);
245
560
  break;
246
561
  }
247
562
  }
248
563
  for (const para of paragraphs) {
249
- if (this.containsSignals(para, this.complicationSignals)) {
250
- complication = this.extractRelevantSentence(para, this.complicationSignals);
564
+ if (this.containsSignals(para.toLowerCase(), this.complicationSignals)) {
565
+ complication = this.extractFirstSentence(para);
251
566
  break;
252
567
  }
253
568
  }
254
- for (const sentence of sentences) {
255
- if (sentence.includes("?") || this.containsSignals(sentence, this.questionSignals)) {
256
- question = sentence;
569
+ const middleStart = Math.floor(paragraphs.length * 0.2);
570
+ const middleEnd = Math.floor(paragraphs.length * 0.8);
571
+ for (const para of paragraphs.slice(middleStart, middleEnd)) {
572
+ const lowerPara = para.toLowerCase();
573
+ if (this.containsSignals(lowerPara, this.answerSignals) && !this.containsSignals(lowerPara, this.ctaSignals)) {
574
+ answer = this.extractFirstSentence(para);
257
575
  break;
258
576
  }
259
577
  }
260
- for (const para of paragraphs.slice(-3)) {
261
- if (this.containsSignals(para, this.answerSignals)) {
262
- answer = this.extractRelevantSentence(para, this.answerSignals);
263
- break;
264
- }
578
+ if (!situation && paragraphs.length > 0 && paragraphs[0]) {
579
+ situation = this.extractFirstSentence(paragraphs[0]);
265
580
  }
266
- if (!situation && paragraphs.length > 0) {
267
- situation = this.truncateToSentence(paragraphs[0] ?? "", 150);
581
+ if (!answer && paragraphs.length > 2) {
582
+ for (const para of paragraphs.slice(1, Math.floor(paragraphs.length * 0.5))) {
583
+ const lowerPara = para.toLowerCase();
584
+ if (lowerPara.includes("recommend") || lowerPara.includes("strategy") || lowerPara.includes("solution") || lowerPara.includes("approach")) {
585
+ answer = this.extractFirstSentence(para);
586
+ break;
587
+ }
588
+ }
268
589
  }
269
- if (!answer && paragraphs.length > 1) {
270
- const lastPara = paragraphs[paragraphs.length - 1];
271
- answer = lastPara ? this.truncateToSentence(lastPara, 150) : "";
590
+ if (complication && !question) {
591
+ question = `How do we address: ${complication.slice(0, 80)}?`;
272
592
  }
273
593
  return { situation, complication, question, answer };
274
594
  }
275
595
  /**
276
- * Extract Sparkline structure (Nancy Duarte).
596
+ * Extract STAR moments (Something They'll Always Remember)
597
+ * Per Nancy Duarte - these are emotional peaks in the presentation
277
598
  */
278
- extractSparkline(paragraphs) {
279
- const whatIs = [];
280
- const whatCouldBe = [];
281
- let callToAdventure = "";
282
- for (const para of paragraphs) {
283
- const lowerPara = para.toLowerCase();
284
- if (this.containsSignals(lowerPara, this.whatIsSignals)) {
285
- whatIs.push(this.truncateToSentence(para, 100));
599
+ extractStarMoments(text) {
600
+ const starMoments = [];
601
+ const boldMatches = text.match(/\*\*(.+?)\*\*/g);
602
+ if (boldMatches) {
603
+ for (const match of boldMatches.slice(0, 5)) {
604
+ const cleaned = match.replace(/\*\*/g, "").trim();
605
+ const lowerCleaned = cleaned.toLowerCase();
606
+ if (cleaned.length > 15 && cleaned.length < 100 && !this.containsSignals(lowerCleaned, this.ctaSignals) && !/^\d+[%x]?\s*$/.test(cleaned)) {
607
+ starMoments.push(cleaned);
608
+ }
286
609
  }
287
- if (this.containsSignals(lowerPara, this.whatCouldBeSignals)) {
288
- whatCouldBe.push(this.truncateToSentence(para, 100));
610
+ }
611
+ const exclamations = text.match(/[^.!?]*![^.!?]*/g);
612
+ if (exclamations) {
613
+ for (const ex of exclamations.slice(0, 2)) {
614
+ const cleaned = ex.trim();
615
+ const lowerCleaned = cleaned.toLowerCase();
616
+ if (cleaned.length > 10 && cleaned.length < 100 && !starMoments.includes(cleaned) && !this.containsSignals(lowerCleaned, this.ctaSignals)) {
617
+ starMoments.push(cleaned);
618
+ }
289
619
  }
290
620
  }
291
- for (const para of paragraphs.slice(-2)) {
292
- if (this.containsSignals(para.toLowerCase(), ["join", "together", "action", "start", "begin", "now"])) {
293
- callToAdventure = this.truncateToSentence(para, 150);
621
+ return starMoments.slice(0, 3);
622
+ }
623
+ /**
624
+ * Extract Sparkline elements (Nancy Duarte)
625
+ */
626
+ extractSparkline(text) {
627
+ const paragraphs = text.split(/\n\n+/).filter((p) => p.trim());
628
+ let callToAction = "";
629
+ const ctaSignals = ["next steps", "call to action", "take action", "start now", "join us", "contact"];
630
+ for (const para of paragraphs.slice(-3)) {
631
+ if (this.containsSignals(para.toLowerCase(), ctaSignals)) {
632
+ callToAction = this.extractFirstSentence(para);
294
633
  break;
295
634
  }
296
635
  }
297
- return { whatIs, whatCouldBe, callToAdventure };
636
+ return { callToAction };
298
637
  }
299
638
  /**
300
- * Extract key messages (max 3 - Rule of Three).
639
+ * Extract key messages (max 3 - Rule of Three)
301
640
  */
302
- extractKeyMessages(text, sentences) {
641
+ extractKeyMessages(text) {
303
642
  const messages = [];
304
- const emphasisMatches = text.match(/\[EMPHASIS\](.+?)\[\/EMPHASIS\]/g);
305
- if (emphasisMatches) {
306
- for (const match of emphasisMatches.slice(0, 3)) {
307
- const content = match.replace(/\[EMPHASIS\]|\[\/EMPHASIS\]/g, "").trim();
308
- if (content.length > 10 && content.length < 100) {
309
- messages.push(content);
310
- }
311
- }
312
- }
313
- const headerMatches = text.match(/\[HEADER\](.+)/g);
314
- if (headerMatches && messages.length < 3) {
315
- for (const match of headerMatches.slice(0, 3 - messages.length)) {
316
- const content = match.replace("[HEADER]", "").trim();
317
- if (content.length > 5 && content.length < 80) {
318
- messages.push(content);
643
+ const h2Matches = text.match(/^##\s+(.+)$/gm);
644
+ if (h2Matches) {
645
+ for (const match of h2Matches.slice(0, 3)) {
646
+ const msg = match.replace(/^##\s+/, "").replace(/\*\*/g, "").trim();
647
+ if (msg.length > 5 && msg.length < 80) {
648
+ messages.push(msg);
319
649
  }
320
650
  }
321
651
  }
322
652
  if (messages.length < 3) {
323
- const strongSentences = sentences.filter((s) => s.length > 20 && s.length < 100).filter((s) => this.containsSignals(s.toLowerCase(), ["key", "important", "critical", "essential", "must", "need"]));
324
- for (const sentence of strongSentences.slice(0, 3 - messages.length)) {
325
- messages.push(sentence);
326
- }
327
- }
328
- if (messages.length < 3) {
329
- for (const sentence of sentences) {
330
- if (sentence.length > 30 && sentence.length < 100 && !messages.includes(sentence)) {
331
- messages.push(sentence);
332
- if (messages.length >= 3) break;
653
+ const boldMatches = text.match(/\*\*(.+?)\*\*/g);
654
+ if (boldMatches) {
655
+ for (const match of boldMatches) {
656
+ const msg = match.replace(/\*\*/g, "").trim();
657
+ if (msg.length > 10 && msg.length < 80 && !messages.includes(msg)) {
658
+ messages.push(msg);
659
+ if (messages.length >= 3) break;
660
+ }
333
661
  }
334
662
  }
335
663
  }
336
664
  return messages.slice(0, 3);
337
665
  }
338
666
  /**
339
- * Generate action titles (McKinsey-style).
667
+ * Extract data points (metrics with values)
340
668
  */
341
- generateActionTitles(keyMessages, paragraphs) {
342
- const titles = [];
343
- for (const message of keyMessages) {
344
- const actionTitle = this.transformToActionTitle(message);
345
- if (actionTitle) {
346
- titles.push(actionTitle);
669
+ extractDataPoints(text) {
670
+ const dataPoints = [];
671
+ const dollarMatches = text.match(/\$[\d,]+(?:\.\d+)?[MBK]?(?:\s*(?:million|billion|thousand))?/gi);
672
+ if (dollarMatches) {
673
+ for (const match of dollarMatches.slice(0, 4)) {
674
+ const context = this.getContextAroundMatch(text, match);
675
+ dataPoints.push({
676
+ value: match,
677
+ label: this.extractLabelFromContext(context)
678
+ });
347
679
  }
348
680
  }
349
- for (const para of paragraphs) {
350
- if (titles.length >= 10) break;
351
- const headerMatch = para.match(/\[HEADER\]\s*(.+)/);
352
- if (headerMatch?.[1]) {
353
- const title = this.transformToActionTitle(headerMatch[1]);
354
- if (title && !titles.includes(title)) {
355
- titles.push(title);
681
+ const percentMatches = text.match(/\d+(?:\.\d+)?%/g);
682
+ if (percentMatches) {
683
+ for (const match of percentMatches.slice(0, 4)) {
684
+ if (!dataPoints.some((d) => d.value === match)) {
685
+ const context = this.getContextAroundMatch(text, match);
686
+ dataPoints.push({
687
+ value: match,
688
+ label: this.extractLabelFromContext(context)
689
+ });
356
690
  }
357
691
  }
358
692
  }
359
- return titles;
693
+ return dataPoints.slice(0, 6);
360
694
  }
361
695
  /**
362
- * Transform a statement into an action title.
696
+ * Get context around a match
363
697
  */
364
- transformToActionTitle(statement) {
365
- let title = statement.replace(/\[(HEADER|EMPHASIS|BULLET|NUMBERED)\]/g, "").trim();
366
- const actionVerbs = ["increase", "decrease", "improve", "reduce", "achieve", "deliver", "create", "build", "launch", "transform", "enable", "drive"];
367
- const firstWord = title.split(" ")[0]?.toLowerCase();
368
- if (firstWord && actionVerbs.includes(firstWord)) {
369
- return this.capitalizeFirst(title);
370
- }
371
- if (title.toLowerCase().includes("should")) {
372
- title = title.replace(/we should|you should|should/gi, "").trim();
373
- return this.capitalizeFirst(title);
374
- }
375
- if (title.toLowerCase().includes("need to")) {
376
- title = title.replace(/we need to|you need to|need to/gi, "").trim();
377
- return this.capitalizeFirst(title);
378
- }
379
- if (title.length < 50) {
380
- return title;
381
- }
382
- return this.truncateToWords(title, 8);
698
+ getContextAroundMatch(text, match) {
699
+ const index = text.indexOf(match);
700
+ if (index === -1) return "";
701
+ const start = Math.max(0, index - 50);
702
+ const end = Math.min(text.length, index + match.length + 50);
703
+ return text.slice(start, end);
383
704
  }
384
705
  /**
385
- * Identify STAR moments (Something They'll Always Remember).
706
+ * Extract a label from surrounding context
707
+ * Fixes the "Century Interactive |" garbage issue by stripping table syntax
386
708
  */
387
- identifyStarMoments(paragraphs) {
388
- const starMoments = [];
389
- const starSignals = [
390
- "surprising",
391
- "amazing",
392
- "incredible",
393
- "remarkable",
394
- "stunning",
395
- "imagine",
396
- "what if",
397
- "breakthrough",
398
- "revolutionary",
399
- "never before",
400
- "first time",
401
- "unprecedented",
402
- "game-changing",
403
- "dramatic"
404
- ];
405
- for (const para of paragraphs) {
406
- if (this.containsSignals(para.toLowerCase(), starSignals)) {
407
- const moment = this.truncateToSentence(para, 120);
408
- if (moment.length > 20) {
409
- starMoments.push(moment);
410
- }
411
- }
412
- }
413
- const statPattern = /\d+[%xX]|\$[\d,]+(?:\s*(?:million|billion|trillion))?|\d+(?:\s*(?:million|billion|trillion))/g;
414
- for (const para of paragraphs) {
415
- if (statPattern.test(para) && starMoments.length < 5) {
416
- const moment = this.truncateToSentence(para, 100);
417
- if (!starMoments.includes(moment)) {
418
- starMoments.push(moment);
419
- }
420
- }
421
- }
422
- return starMoments.slice(0, 5);
709
+ extractLabelFromContext(context) {
710
+ let cleaned = context.replace(/\|/g, " ").replace(/-{3,}/g, "").replace(/\s{2,}/g, " ").replace(/\*\*/g, "").replace(/#+\s*/g, "").trim();
711
+ const words = cleaned.split(/\s+/).filter((w) => w.length > 2 && !w.match(/^\d/));
712
+ return words.slice(0, 4).join(" ") || "Value";
423
713
  }
424
714
  /**
425
- * Estimate slide count based on content.
715
+ * Check if text contains any of the signals
426
716
  */
427
- estimateSlideCount(text, paragraphs) {
428
- const wordCount = text.split(/\s+/).length;
429
- const headerCount = (text.match(/\[HEADER\]/g) ?? []).length;
430
- const bulletGroups = (text.match(/\[BULLET\]/g) ?? []).length / 4;
431
- const wordBasedEstimate = Math.ceil(wordCount / 35);
432
- const estimate = Math.max(
433
- 5,
434
- // Minimum 5 slides
435
- Math.ceil((wordBasedEstimate + headerCount + bulletGroups) / 2)
436
- );
437
- return Math.min(estimate, 30);
438
- }
439
- // === Helper Methods ===
440
717
  containsSignals(text, signals) {
441
- const lowerText = text.toLowerCase();
442
- return signals.some((signal) => lowerText.includes(signal));
443
- }
444
- extractRelevantSentence(paragraph, signals) {
445
- const sentences = paragraph.split(/[.!?]+/);
446
- for (const sentence of sentences) {
447
- if (this.containsSignals(sentence.toLowerCase(), signals)) {
448
- return sentence.trim();
449
- }
450
- }
451
- return this.truncateToSentence(paragraph, 150);
718
+ return signals.some((signal) => text.includes(signal));
452
719
  }
453
- truncateToSentence(text, maxLength) {
454
- if (text.length <= maxLength) {
455
- return text.trim();
456
- }
457
- const truncated = text.slice(0, maxLength);
458
- const lastPeriod = truncated.lastIndexOf(".");
459
- const lastQuestion = truncated.lastIndexOf("?");
460
- const lastExclaim = truncated.lastIndexOf("!");
461
- const lastBoundary = Math.max(lastPeriod, lastQuestion, lastExclaim);
462
- if (lastBoundary > maxLength * 0.5) {
463
- return text.slice(0, lastBoundary + 1).trim();
464
- }
465
- return truncated.trim() + "...";
466
- }
467
- truncateToWords(text, maxWords) {
468
- const words = text.split(/\s+/);
469
- if (words.length <= maxWords) {
470
- return text;
720
+ /**
721
+ * Extract first meaningful sentence from text
722
+ */
723
+ extractFirstSentence(text) {
724
+ const cleaned = text.replace(/^#+\s*/gm, "").replace(/\*\*/g, "").replace(/\|/g, " ").trim();
725
+ const sentences = cleaned.split(/[.!?]+/);
726
+ const first = sentences[0]?.trim();
727
+ if (first && first.length > 10) {
728
+ return first.slice(0, 150);
471
729
  }
472
- return words.slice(0, maxWords).join(" ");
473
- }
474
- capitalizeFirst(text) {
475
- if (!text) return "";
476
- return text.charAt(0).toUpperCase() + text.slice(1);
730
+ return cleaned.slice(0, 150);
477
731
  }
478
732
  };
479
733
 
480
734
  // src/core/SlideFactory.ts
481
735
  var SlideFactory = class {
482
736
  templates;
737
+ usedContent = /* @__PURE__ */ new Set();
483
738
  constructor() {
484
739
  this.templates = this.initializeTemplates();
485
740
  }
741
+ /**
742
+ * Check if content has already been used (deduplication)
743
+ */
744
+ isContentUsed(content) {
745
+ if (!content) return true;
746
+ const normalized = content.toLowerCase().replace(/[^a-z0-9]/g, "").slice(0, 50);
747
+ if (this.usedContent.has(normalized)) {
748
+ return true;
749
+ }
750
+ this.usedContent.add(normalized);
751
+ return false;
752
+ }
486
753
  /**
487
754
  * Create slides from analyzed content.
488
755
  */
489
756
  async createSlides(analysis, mode) {
490
757
  const slides = [];
491
758
  let slideIndex = 0;
759
+ this.usedContent.clear();
492
760
  slides.push(this.createTitleSlide(slideIndex++, analysis));
761
+ this.isContentUsed(analysis.titles[0] ?? "");
493
762
  if (mode === "business" && analysis.keyMessages.length >= 2) {
494
763
  slides.push(this.createAgendaSlide(slideIndex++, analysis));
495
764
  }
496
- if (analysis.scqa.situation) {
765
+ if (analysis.scqa.situation && !this.isContentUsed(analysis.scqa.situation)) {
497
766
  slides.push(this.createContextSlide(slideIndex++, analysis, mode));
498
767
  }
499
- if (analysis.scqa.complication) {
768
+ if (analysis.scqa.complication && !this.isContentUsed(analysis.scqa.complication)) {
500
769
  slides.push(this.createProblemSlide(slideIndex++, analysis, mode));
501
770
  }
502
771
  for (const message of analysis.keyMessages) {
503
- slides.push(this.createMessageSlide(slideIndex++, message, mode));
772
+ if (!this.isContentUsed(message)) {
773
+ slides.push(this.createMessageSlide(slideIndex++, message, mode));
774
+ }
504
775
  }
505
776
  for (const starMoment of analysis.starMoments.slice(0, 2)) {
506
- slides.push(this.createStarMomentSlide(slideIndex++, starMoment, mode));
777
+ if (!this.isContentUsed(starMoment)) {
778
+ slides.push(this.createStarMomentSlide(slideIndex++, starMoment, mode));
779
+ }
507
780
  }
508
- if (analysis.scqa.answer) {
781
+ if (analysis.scqa.answer && !this.isContentUsed(analysis.scqa.answer)) {
509
782
  slides.push(this.createSolutionSlide(slideIndex++, analysis, mode));
510
783
  }
511
- if (analysis.sparkline.callToAdventure) {
784
+ if (analysis.sparkline.callToAdventure && !this.isContentUsed(analysis.sparkline.callToAdventure)) {
512
785
  slides.push(this.createCTASlide(slideIndex++, analysis, mode));
513
786
  }
514
787
  slides.push(this.createThankYouSlide(slideIndex++));
@@ -518,13 +791,18 @@ var SlideFactory = class {
518
791
  * Create a title slide.
519
792
  */
520
793
  createTitleSlide(index, analysis) {
521
- const subtitle = analysis.keyMessages[0] ?? analysis.scqa.answer ?? "";
794
+ let subtitle = "";
795
+ if (analysis.scqa.answer && analysis.scqa.answer.length > 10) {
796
+ subtitle = analysis.scqa.answer;
797
+ } else if (analysis.starMoments.length > 0 && analysis.starMoments[0]) {
798
+ subtitle = analysis.starMoments[0];
799
+ }
522
800
  return {
523
801
  index,
524
802
  type: "title",
525
803
  data: {
526
804
  title: analysis.titles[0] ?? "Presentation",
527
- subtitle: this.truncate(subtitle, 60),
805
+ subtitle: this.truncate(subtitle, 80),
528
806
  keyMessage: analysis.scqa.answer
529
807
  },
530
808
  classes: ["slide-title"]
@@ -1755,19 +2033,28 @@ var ScoreCalculator = class {
1755
2033
  import { chromium } from "playwright";
1756
2034
  var QAEngine = class {
1757
2035
  browser = null;
2036
+ kb;
2037
+ /**
2038
+ * Initialize KnowledgeGateway
2039
+ */
2040
+ async initialize() {
2041
+ this.kb = await getKnowledgeGateway();
2042
+ }
1758
2043
  /**
1759
- * Validate a presentation.
2044
+ * Validate a presentation using KB-driven quality metrics
1760
2045
  */
1761
2046
  async validate(presentation, options) {
2047
+ await this.initialize();
1762
2048
  const html = typeof presentation === "string" ? presentation : presentation.toString("utf-8");
1763
2049
  const mode = options?.mode ?? "keynote";
2050
+ const qualityMetrics = this.kb.getQualityMetrics();
1764
2051
  await this.initBrowser();
1765
2052
  try {
1766
2053
  const [visualResults, contentResults, expertResults, accessibilityResults] = await Promise.all([
1767
- this.runVisualTests(html, mode),
1768
- this.runContentTests(html, mode),
2054
+ this.runVisualTests(html, mode, qualityMetrics),
2055
+ this.runContentTests(html, mode, qualityMetrics),
1769
2056
  this.runExpertTests(html, mode),
1770
- this.runAccessibilityTests(html)
2057
+ this.runAccessibilityTests(html, qualityMetrics)
1771
2058
  ]);
1772
2059
  const issues = this.collectIssues(visualResults, contentResults, expertResults, accessibilityResults);
1773
2060
  const errorCount = issues.filter((i) => i.severity === "error").length;
@@ -1785,18 +2072,18 @@ var QAEngine = class {
1785
2072
  }
1786
2073
  }
1787
2074
  /**
1788
- * Calculate overall QA score.
2075
+ * Calculate overall QA score using KB-driven weights
1789
2076
  */
1790
2077
  calculateScore(results) {
1791
2078
  const weights = {
1792
2079
  visual: 0.35,
1793
- // 35%
2080
+ // 35% - Design quality
1794
2081
  content: 0.3,
1795
- // 30%
2082
+ // 30% - Content quality
1796
2083
  expert: 0.25,
1797
- // 25%
2084
+ // 25% - Expert methodology
1798
2085
  accessibility: 0.1
1799
- // 10%
2086
+ // 10% - Accessibility
1800
2087
  };
1801
2088
  const visualScore = this.calculateVisualScore(results.visual);
1802
2089
  const contentScore = this.calculateContentScore(results.content);
@@ -1843,13 +2130,15 @@ var QAEngine = class {
1843
2130
  };
1844
2131
  }
1845
2132
  // ===========================================================================
1846
- // VISUAL TESTS
2133
+ // VISUAL TESTS - KB-DRIVEN
1847
2134
  // ===========================================================================
1848
- async runVisualTests(html, mode) {
2135
+ async runVisualTests(html, mode, qualityMetrics) {
1849
2136
  const page = await this.browser.newPage();
1850
2137
  await page.setViewportSize({ width: 1280, height: 720 });
1851
2138
  await page.setContent(html);
1852
2139
  await page.waitForTimeout(1e3);
2140
+ const modeMetrics = mode === "keynote" ? qualityMetrics.keynote_mode : qualityMetrics.business_mode;
2141
+ const minWhitespace = this.kb.getWhitespaceRequirement(mode) * 100;
1853
2142
  const slideCount = await page.evaluate(() => {
1854
2143
  return window.Reveal?.getTotalSlides?.() ?? document.querySelectorAll(".slides > section").length;
1855
2144
  });
@@ -1915,9 +2204,8 @@ var QAEngine = class {
1915
2204
  }, { slideIndex: i });
1916
2205
  if (slideAnalysis) {
1917
2206
  const issues = [];
1918
- const minWhitespace = mode === "keynote" ? 40 : 25;
1919
2207
  if (slideAnalysis.whitespace < minWhitespace) {
1920
- issues.push(`Whitespace ${slideAnalysis.whitespace}% below ${minWhitespace}% minimum`);
2208
+ issues.push(`Whitespace ${slideAnalysis.whitespace}% below ${minWhitespace}% minimum (KB-defined)`);
1921
2209
  }
1922
2210
  if (slideAnalysis.whitespace > 80) {
1923
2211
  issues.push(`Whitespace ${slideAnalysis.whitespace}% - slide appears sparse`);
@@ -1972,34 +2260,35 @@ var QAEngine = class {
1972
2260
  };
1973
2261
  }
1974
2262
  // ===========================================================================
1975
- // CONTENT TESTS
2263
+ // CONTENT TESTS - KB-DRIVEN
1976
2264
  // ===========================================================================
1977
- async runContentTests(html, mode) {
2265
+ async runContentTests(html, mode, qualityMetrics) {
1978
2266
  const page = await this.browser.newPage();
1979
2267
  await page.setContent(html);
1980
2268
  await page.waitForTimeout(500);
1981
- const results = await page.evaluate((targetMode) => {
2269
+ const wordLimits = this.kb.getWordLimits(mode);
2270
+ const glanceTest = this.kb.getDuarteGlanceTest();
2271
+ const results = await page.evaluate((params) => {
2272
+ const { targetMode, maxWords, minWords, glanceWordLimit } = params;
1982
2273
  const slides = document.querySelectorAll(".slides > section");
1983
2274
  const perSlide = [];
1984
- const glanceTest = [];
2275
+ const glanceTestResults = [];
1985
2276
  const signalNoise = [];
1986
2277
  const oneIdea = [];
1987
2278
  slides.forEach((slide, index) => {
1988
2279
  const text = slide.innerText || "";
1989
2280
  const words = text.split(/\s+/).filter((w) => w.length > 0);
1990
2281
  const wordCount = words.length;
1991
- const maxWords = targetMode === "keynote" ? 25 : 80;
1992
- const minWords = targetMode === "business" ? 20 : 0;
1993
2282
  const withinLimit = wordCount <= maxWords && wordCount >= minWords;
1994
2283
  const title = slide.querySelector("h2")?.textContent || "";
1995
2284
  const hasVerb = /\b(is|are|was|were|has|have|had|will|can|could|should|would|may|might|must|exceeded|increased|decreased|grew|fell|drove|caused|enabled|prevented|achieved|failed|creates?|generates?|delivers?|provides?|shows?|demonstrates?)\b/i.test(title);
1996
2285
  const hasInsight = title.length > 30 && hasVerb;
1997
2286
  const issues = [];
1998
2287
  if (!withinLimit) {
1999
- issues.push(`Word count ${wordCount} outside ${minWords}-${maxWords} range`);
2288
+ issues.push(`Word count ${wordCount} outside ${minWords}-${maxWords} range (KB-defined)`);
2000
2289
  }
2001
2290
  if (targetMode === "business" && !hasInsight && index > 0) {
2002
- issues.push("Title is not action-oriented (missing insight)");
2291
+ issues.push("Title is not action-oriented (missing insight per Minto)");
2003
2292
  }
2004
2293
  perSlide.push({
2005
2294
  slideIndex: index,
@@ -2012,12 +2301,12 @@ var QAEngine = class {
2012
2301
  const keyMessage = prominentElement?.textContent?.trim() || "";
2013
2302
  const keyWordCount = keyMessage.split(/\s+/).filter((w) => w.length > 0).length;
2014
2303
  const readingTime = keyWordCount / 4.2;
2015
- glanceTest.push({
2304
+ glanceTestResults.push({
2016
2305
  slideIndex: index,
2017
2306
  keyMessage,
2018
2307
  wordCount: keyWordCount,
2019
2308
  readingTime: Math.round(readingTime * 10) / 10,
2020
- passed: readingTime <= 3 && keyWordCount <= 15,
2309
+ passed: readingTime <= 3 && keyWordCount <= glanceWordLimit,
2021
2310
  recommendation: readingTime > 3 ? `Shorten to ${Math.floor(3 * 4.2)} words or less` : void 0
2022
2311
  });
2023
2312
  const elements = slide.querySelectorAll("h1, h2, h3, p, li, img");
@@ -2044,35 +2333,143 @@ var QAEngine = class {
2044
2333
  conflictingIdeas: ideaCount > 2 ? ["Multiple competing ideas detected"] : void 0
2045
2334
  });
2046
2335
  });
2047
- return { perSlide, glanceTest, signalNoise, oneIdea };
2048
- }, mode);
2336
+ return { perSlide, glanceTest: glanceTestResults, signalNoise, oneIdea };
2337
+ }, {
2338
+ targetMode: mode,
2339
+ maxWords: wordLimits.max,
2340
+ minWords: mode === "business" ? 20 : 0,
2341
+ glanceWordLimit: glanceTest.wordLimit
2342
+ });
2049
2343
  await page.close();
2050
2344
  return results;
2051
2345
  }
2052
2346
  // ===========================================================================
2053
- // EXPERT TESTS
2347
+ // EXPERT TESTS - KB-DRIVEN
2054
2348
  // ===========================================================================
2055
2349
  async runExpertTests(html, mode) {
2350
+ const duartePrinciples = this.kb.getExpertPrinciples("nancy_duarte");
2351
+ const reynoldsPrinciples = this.kb.getExpertPrinciples("garr_reynolds");
2352
+ const galloPrinciples = this.kb.getExpertPrinciples("carmine_gallo");
2353
+ const andersonPrinciples = this.kb.getExpertPrinciples("chris_anderson");
2056
2354
  return {
2057
- duarte: this.createExpertResult("Nancy Duarte", ["Glance Test", "STAR Moment", "Sparkline"], 85),
2058
- reynolds: this.createExpertResult("Garr Reynolds", ["Signal-to-Noise", "Simplicity", "Picture Superiority"], 80),
2059
- gallo: this.createExpertResult("Carmine Gallo", ["Rule of Three", "Emotional Connection"], 85),
2060
- anderson: this.createExpertResult("Chris Anderson", ["One Idea", "Clarity"], 90)
2355
+ duarte: this.validateDuartePrinciples(html, duartePrinciples),
2356
+ reynolds: this.validateReynoldsPrinciples(html, reynoldsPrinciples),
2357
+ gallo: this.validateGalloPrinciples(html, galloPrinciples),
2358
+ anderson: this.validateAndersonPrinciples(html, andersonPrinciples)
2061
2359
  };
2062
2360
  }
2063
- createExpertResult(name, principles, score) {
2361
+ validateDuartePrinciples(html, principles) {
2362
+ const violations = [];
2363
+ let score = 100;
2364
+ const slideMatches = html.match(/<section[^>]*>[\s\S]*?<\/section>/gi) || [];
2365
+ slideMatches.forEach((slide, index) => {
2366
+ const h1Match = slide.match(/<h1[^>]*>([\s\S]*?)<\/h1>/i);
2367
+ const h2Match = slide.match(/<h2[^>]*>([\s\S]*?)<\/h2>/i);
2368
+ const keyMessage = (h1Match?.[1] || h2Match?.[1] || "").replace(/<[^>]+>/g, "").trim();
2369
+ const wordCount = keyMessage.split(/\s+/).filter((w) => w.length > 0).length;
2370
+ if (wordCount > 25) {
2371
+ violations.push(`Slide ${index + 1}: Key message exceeds 25 words (Duarte Glance Test)`);
2372
+ score -= 5;
2373
+ }
2374
+ });
2375
+ const hasStarMoment = html.includes("callout") || html.includes("highlight") || html.includes("big-idea");
2376
+ if (!hasStarMoment && slideMatches.length > 5) {
2377
+ violations.push("Missing STAR moment (Something They'll Always Remember)");
2378
+ score -= 10;
2379
+ }
2064
2380
  return {
2065
- expertName: name,
2066
- principlesChecked: principles,
2381
+ expertName: "Nancy Duarte",
2382
+ principlesChecked: ["Glance Test", "STAR Moment", "Sparkline Structure"],
2067
2383
  passed: score >= 80,
2068
- score,
2069
- violations: score < 80 ? [`${name} principles not fully met`] : []
2384
+ score: Math.max(0, score),
2385
+ violations
2386
+ };
2387
+ }
2388
+ validateReynoldsPrinciples(html, principles) {
2389
+ const violations = [];
2390
+ let score = 100;
2391
+ const decorativeElements = (html.match(/class="[^"]*decorative[^"]*"/gi) || []).length;
2392
+ const randomImages = (html.match(/picsum\.photos/gi) || []).length;
2393
+ if (randomImages > 0) {
2394
+ violations.push(`Found ${randomImages} random stock images (Reynolds: only use purposeful images)`);
2395
+ score -= randomImages * 10;
2396
+ }
2397
+ if (decorativeElements > 3) {
2398
+ violations.push(`Too many decorative elements (${decorativeElements}) - reduces signal-to-noise ratio`);
2399
+ score -= 10;
2400
+ }
2401
+ const columnLayouts = (html.match(/class="[^"]*columns[^"]*"/gi) || []).length;
2402
+ const slideCount = (html.match(/<section/gi) || []).length;
2403
+ if (columnLayouts > slideCount * 0.5) {
2404
+ violations.push("Overuse of column layouts - simplify per Reynolds");
2405
+ score -= 10;
2406
+ }
2407
+ return {
2408
+ expertName: "Garr Reynolds",
2409
+ principlesChecked: ["Signal-to-Noise", "Simplicity", "Picture Superiority"],
2410
+ passed: score >= 80,
2411
+ score: Math.max(0, score),
2412
+ violations
2413
+ };
2414
+ }
2415
+ validateGalloPrinciples(html, principles) {
2416
+ const violations = [];
2417
+ let score = 100;
2418
+ const bulletLists = html.match(/<ul[^>]*>[\s\S]*?<\/ul>/gi) || [];
2419
+ bulletLists.forEach((list, index) => {
2420
+ const bullets = (list.match(/<li/gi) || []).length;
2421
+ if (bullets > 5) {
2422
+ violations.push(`Bullet list ${index + 1} has ${bullets} items (Gallo: max 3-5)`);
2423
+ score -= 5;
2424
+ }
2425
+ });
2426
+ const hasQuote = html.includes("blockquote") || html.includes("quote");
2427
+ const hasStory = html.toLowerCase().includes("story") || html.toLowerCase().includes("journey");
2428
+ if (!hasQuote && !hasStory) {
2429
+ violations.push("Missing emotional connection elements (quotes, stories)");
2430
+ score -= 10;
2431
+ }
2432
+ return {
2433
+ expertName: "Carmine Gallo",
2434
+ principlesChecked: ["Rule of Three", "Emotional Connection", "Headline Power"],
2435
+ passed: score >= 80,
2436
+ score: Math.max(0, score),
2437
+ violations
2438
+ };
2439
+ }
2440
+ validateAndersonPrinciples(html, principles) {
2441
+ const violations = [];
2442
+ let score = 100;
2443
+ const slideMatches = html.match(/<section[^>]*>[\s\S]*?<\/section>/gi) || [];
2444
+ slideMatches.forEach((slide, index) => {
2445
+ const headings = (slide.match(/<h[12][^>]*>/gi) || []).length;
2446
+ if (headings > 2) {
2447
+ violations.push(`Slide ${index + 1}: Multiple ideas detected (Anderson: one idea per slide)`);
2448
+ score -= 5;
2449
+ }
2450
+ });
2451
+ const jargonPatterns = ["synergy", "leverage", "paradigm", "holistic", "streamline"];
2452
+ let jargonCount = 0;
2453
+ jargonPatterns.forEach((pattern) => {
2454
+ const matches = html.toLowerCase().match(new RegExp(pattern, "gi")) || [];
2455
+ jargonCount += matches.length;
2456
+ });
2457
+ if (jargonCount > 5) {
2458
+ violations.push(`Excessive jargon detected (${jargonCount} instances) - simplify language`);
2459
+ score -= 10;
2460
+ }
2461
+ return {
2462
+ expertName: "Chris Anderson",
2463
+ principlesChecked: ["One Idea", "Clarity", "Throughline"],
2464
+ passed: score >= 80,
2465
+ score: Math.max(0, score),
2466
+ violations
2070
2467
  };
2071
2468
  }
2072
2469
  // ===========================================================================
2073
- // ACCESSIBILITY TESTS
2470
+ // ACCESSIBILITY TESTS - KB-DRIVEN
2074
2471
  // ===========================================================================
2075
- async runAccessibilityTests(html) {
2472
+ async runAccessibilityTests(html, qualityMetrics) {
2076
2473
  const page = await this.browser.newPage();
2077
2474
  await page.setContent(html);
2078
2475
  await page.waitForTimeout(500);
@@ -2122,7 +2519,7 @@ var QAEngine = class {
2122
2519
  };
2123
2520
  }
2124
2521
  // ===========================================================================
2125
- // SCORING
2522
+ // SCORING - KB-DRIVEN
2126
2523
  // ===========================================================================
2127
2524
  calculateVisualScore(results) {
2128
2525
  let score = 100;
@@ -2246,6 +2643,7 @@ var QAEngine = class {
2246
2643
  // src/generators/html/RevealJsGenerator.ts
2247
2644
  var RevealJsGenerator = class {
2248
2645
  templateEngine;
2646
+ kb;
2249
2647
  defaultRevealConfig = {
2250
2648
  revealVersion: "5.0.4",
2251
2649
  hash: true,
@@ -2264,20 +2662,32 @@ var RevealJsGenerator = class {
2264
2662
  constructor() {
2265
2663
  this.templateEngine = new TemplateEngine();
2266
2664
  }
2665
+ /**
2666
+ * Initialize KnowledgeGateway
2667
+ */
2668
+ async initialize() {
2669
+ this.kb = await getKnowledgeGateway();
2670
+ }
2267
2671
  /**
2268
2672
  * Generate complete Reveal.js HTML presentation.
2269
2673
  */
2270
2674
  async generate(slides, config) {
2675
+ await this.initialize();
2271
2676
  const templateConfig = {};
2272
2677
  if (config.theme) templateConfig.theme = config.theme;
2273
2678
  if (config.customTemplates) templateConfig.customTemplates = config.customTemplates;
2274
2679
  const slideHtml = this.templateEngine.renderAll(slides, templateConfig);
2680
+ const presentationType = this.detectPresentationType(config);
2681
+ const palette = this.kb.getRecommendedPalette(presentationType);
2682
+ const typography = this.kb.getTypography(config.mode);
2275
2683
  const docConfig = {
2276
2684
  title: config.title,
2277
2685
  slides: slideHtml.join("\n"),
2278
2686
  theme: config.theme ?? "default",
2279
2687
  revealConfig: this.defaultRevealConfig,
2280
- mode: config.mode
2688
+ mode: config.mode,
2689
+ palette,
2690
+ typography
2281
2691
  };
2282
2692
  if (config.author) docConfig.author = config.author;
2283
2693
  if (config.subject) docConfig.subject = config.subject;
@@ -2288,11 +2698,23 @@ var RevealJsGenerator = class {
2288
2698
  }
2289
2699
  return html;
2290
2700
  }
2701
+ /**
2702
+ * Detect presentation type from config
2703
+ */
2704
+ detectPresentationType(config) {
2705
+ if (config.presentationType) {
2706
+ return config.presentationType;
2707
+ }
2708
+ if (config.mode === "keynote") {
2709
+ return "ted_keynote";
2710
+ }
2711
+ return "consulting_deck";
2712
+ }
2291
2713
  /**
2292
2714
  * Build the complete HTML document.
2293
2715
  */
2294
2716
  buildDocument(options) {
2295
- const { title, author, subject, slides, theme, customCSS, revealConfig, mode } = options;
2717
+ const { title, author, subject, slides, theme, customCSS, revealConfig, mode, palette, typography } = options;
2296
2718
  return `<!DOCTYPE html>
2297
2719
  <html lang="en">
2298
2720
  <head>
@@ -2300,7 +2722,7 @@ var RevealJsGenerator = class {
2300
2722
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
2301
2723
  <meta name="author" content="${this.escapeHtml(author ?? "Claude Presentation Master")}">
2302
2724
  <meta name="description" content="${this.escapeHtml(subject ?? "")}">
2303
- <meta name="generator" content="Claude Presentation Master v1.0.0">
2725
+ <meta name="generator" content="Claude Presentation Master v6.0.0">
2304
2726
  <title>${this.escapeHtml(title)}</title>
2305
2727
 
2306
2728
  <!-- Reveal.js CSS -->
@@ -2314,10 +2736,10 @@ var RevealJsGenerator = class {
2314
2736
  <!-- Mermaid for diagrams -->
2315
2737
  <script src="https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js"></script>
2316
2738
 
2317
- <!-- Presentation Engine CSS -->
2739
+ <!-- Presentation Engine CSS - KB-Driven Design System -->
2318
2740
  <style>
2319
- ${this.getBaseStyles(mode)}
2320
- ${this.getThemeStyles(theme)}
2741
+ ${this.getBaseStyles(mode, palette, typography)}
2742
+ ${this.getThemeStyles(theme, palette)}
2321
2743
  ${this.getAnimationStyles()}
2322
2744
  ${customCSS ?? ""}
2323
2745
  </style>
@@ -2369,26 +2791,35 @@ ${slides}
2369
2791
  </html>`;
2370
2792
  }
2371
2793
  /**
2372
- * Get base styles for slides.
2794
+ * Get base styles for slides - KB-DRIVEN
2373
2795
  */
2374
- getBaseStyles(mode) {
2796
+ getBaseStyles(mode, palette, typography) {
2375
2797
  const fontSize = mode === "keynote" ? "2.5em" : "1.8em";
2376
2798
  const lineHeight = mode === "keynote" ? "1.4" : "1.5";
2799
+ const primary = palette.primary || "#1a1a2e";
2800
+ const secondary = palette.secondary || "#16213e";
2801
+ const accent = palette.accent || "#0f3460";
2802
+ const highlight = palette.accent || "#e94560";
2803
+ const text = palette.text || "#1a1a2e";
2804
+ const background = palette.background || "#ffffff";
2377
2805
  return `
2378
- /* Base Styles */
2806
+ /* Base Styles - KB-Driven Design System */
2379
2807
  :root {
2808
+ /* Typography from KB */
2380
2809
  --font-heading: 'Source Sans Pro', 'Helvetica Neue', -apple-system, BlinkMacSystemFont, sans-serif;
2381
2810
  --font-body: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
2382
2811
  --font-mono: 'SF Mono', 'Fira Code', 'JetBrains Mono', monospace;
2383
2812
 
2384
- --color-primary: #1a1a2e;
2385
- --color-secondary: #16213e;
2386
- --color-accent: #0f3460;
2387
- --color-highlight: #e94560;
2388
- --color-text: #1a1a2e;
2389
- --color-text-light: #4a4a68;
2390
- --color-background: #ffffff;
2813
+ /* Colors from KB Palette: ${palette.name} */
2814
+ --color-primary: ${primary};
2815
+ --color-secondary: ${secondary};
2816
+ --color-accent: ${accent};
2817
+ --color-highlight: ${highlight};
2818
+ --color-text: ${text};
2819
+ --color-text-light: ${this.lightenColor(text, 30)};
2820
+ --color-background: ${background};
2391
2821
 
2822
+ /* Layout */
2392
2823
  --slide-padding: 60px;
2393
2824
  --content-max-width: 1200px;
2394
2825
  }
@@ -2504,7 +2935,7 @@ ${slides}
2504
2935
  font-size: 0.8em;
2505
2936
  }
2506
2937
 
2507
- /* Images */
2938
+ /* Images - NO RANDOM PICSUM - Only user-provided or none */
2508
2939
  .reveal img {
2509
2940
  max-width: 100%;
2510
2941
  height: auto;
@@ -2610,9 +3041,25 @@ ${slides}
2610
3041
  `;
2611
3042
  }
2612
3043
  /**
2613
- * Get theme-specific styles.
3044
+ * Lighten a hex color
3045
+ */
3046
+ lightenColor(hex, percent) {
3047
+ hex = hex.replace("#", "");
3048
+ let r = parseInt(hex.substring(0, 2), 16);
3049
+ let g = parseInt(hex.substring(2, 4), 16);
3050
+ let b = parseInt(hex.substring(4, 6), 16);
3051
+ r = Math.min(255, Math.floor(r + (255 - r) * (percent / 100)));
3052
+ g = Math.min(255, Math.floor(g + (255 - g) * (percent / 100)));
3053
+ b = Math.min(255, Math.floor(b + (255 - b) * (percent / 100)));
3054
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
3055
+ }
3056
+ /**
3057
+ * Get theme-specific styles - KB-DRIVEN
2614
3058
  */
2615
- getThemeStyles(theme) {
3059
+ getThemeStyles(theme, palette) {
3060
+ if (theme === "default") {
3061
+ return "";
3062
+ }
2616
3063
  const themes = {
2617
3064
  "default": "",
2618
3065
  "light-corporate": `
@@ -2626,10 +3073,10 @@ ${slides}
2626
3073
  `,
2627
3074
  "modern-tech": `
2628
3075
  :root {
2629
- --color-primary: #1a1a2e;
2630
- --color-secondary: #16213e;
2631
- --color-accent: #0f3460;
2632
- --color-highlight: #e94560;
3076
+ --color-primary: ${palette.primary || "#1a1a2e"};
3077
+ --color-secondary: ${palette.secondary || "#16213e"};
3078
+ --color-accent: ${palette.accent || "#0f3460"};
3079
+ --color-highlight: ${palette.accent || "#e94560"};
2633
3080
  --color-background: #f8f9fa;
2634
3081
  }
2635
3082
  `,
@@ -3875,239 +4322,6 @@ function createDefaultImageProvider(options) {
3875
4322
  return new CompositeImageProvider(providers);
3876
4323
  }
3877
4324
 
3878
- // src/knowledge/KnowledgeBase.ts
3879
- import { readFileSync } from "fs";
3880
- import { join, dirname } from "path";
3881
- import { fileURLToPath } from "url";
3882
- import * as yaml from "yaml";
3883
- function getModuleDir() {
3884
- if (typeof import.meta !== "undefined" && import.meta.url) {
3885
- return dirname(fileURLToPath(import.meta.url));
3886
- }
3887
- if (typeof __dirname !== "undefined") {
3888
- return __dirname;
3889
- }
3890
- return process.cwd();
3891
- }
3892
- var moduleDir = getModuleDir();
3893
- var KnowledgeBase = class {
3894
- data = null;
3895
- loaded = false;
3896
- /**
3897
- * Load the knowledge base from the bundled YAML file.
3898
- */
3899
- async load() {
3900
- if (this.loaded) return;
3901
- try {
3902
- const possiblePaths = [
3903
- // From dist/: go up to package root
3904
- join(moduleDir, "../../assets/presentation-knowledge.yaml"),
3905
- join(moduleDir, "../assets/presentation-knowledge.yaml"),
3906
- join(moduleDir, "../../../assets/presentation-knowledge.yaml"),
3907
- // From bundle in dist/index.js: go up one level
3908
- join(moduleDir, "assets/presentation-knowledge.yaml"),
3909
- // CWD-based paths for development or local installs
3910
- join(process.cwd(), "assets/presentation-knowledge.yaml"),
3911
- join(process.cwd(), "node_modules/claude-presentation-master/assets/presentation-knowledge.yaml")
3912
- ];
3913
- let assetPath = "";
3914
- for (const p of possiblePaths) {
3915
- try {
3916
- readFileSync(p);
3917
- assetPath = p;
3918
- break;
3919
- } catch {
3920
- continue;
3921
- }
3922
- }
3923
- if (!assetPath) {
3924
- throw new Error("Could not locate knowledge base file");
3925
- }
3926
- const content = readFileSync(assetPath, "utf-8");
3927
- this.data = yaml.parse(content);
3928
- this.loaded = true;
3929
- console.log(`\u{1F4DA} Knowledge base loaded: v${this.data.version}`);
3930
- } catch (error) {
3931
- console.warn("\u26A0\uFE0F Could not load knowledge base, using defaults");
3932
- this.data = this.getDefaultData();
3933
- this.loaded = true;
3934
- }
3935
- }
3936
- /**
3937
- * Get expert methodology by name.
3938
- */
3939
- getExpert(name) {
3940
- this.ensureLoaded();
3941
- return this.data?.experts?.[name];
3942
- }
3943
- /**
3944
- * Get all expert names.
3945
- */
3946
- getExpertNames() {
3947
- this.ensureLoaded();
3948
- return Object.keys(this.data?.experts ?? {});
3949
- }
3950
- /**
3951
- * Get framework recommendation for audience.
3952
- */
3953
- getFrameworkForAudience(audience) {
3954
- this.ensureLoaded();
3955
- return this.data?.frameworkSelector?.byAudience?.[audience];
3956
- }
3957
- /**
3958
- * Get framework recommendation for goal.
3959
- */
3960
- getFrameworkForGoal(goal) {
3961
- this.ensureLoaded();
3962
- return this.data?.frameworkSelector?.byGoal?.[goal];
3963
- }
3964
- /**
3965
- * Get QA scoring rubric.
3966
- */
3967
- getScoringRubric() {
3968
- this.ensureLoaded();
3969
- return this.data?.automatedQA?.scoringRubric;
3970
- }
3971
- /**
3972
- * Get mode configuration (keynote or business).
3973
- */
3974
- getModeConfig(mode) {
3975
- this.ensureLoaded();
3976
- return this.data?.modes?.[mode];
3977
- }
3978
- /**
3979
- * Get slide type configuration.
3980
- */
3981
- getSlideType(type) {
3982
- this.ensureLoaded();
3983
- return this.data?.slideTypes?.[type];
3984
- }
3985
- /**
3986
- * Get the knowledge base version.
3987
- */
3988
- getVersion() {
3989
- this.ensureLoaded();
3990
- return this.data?.version ?? "unknown";
3991
- }
3992
- /**
3993
- * Validate a slide against expert principles.
3994
- */
3995
- validateAgainstExpert(expertName, slideData) {
3996
- const expert = this.getExpert(expertName);
3997
- if (!expert) {
3998
- return { passed: true, violations: [] };
3999
- }
4000
- const violations = [];
4001
- if (expert.wordLimits) {
4002
- if (expert.wordLimits.max && slideData.wordCount > expert.wordLimits.max) {
4003
- violations.push(`Exceeds ${expertName} word limit of ${expert.wordLimits.max}`);
4004
- }
4005
- if (expert.wordLimits.min && slideData.wordCount < expert.wordLimits.min) {
4006
- violations.push(`Below ${expertName} minimum of ${expert.wordLimits.min} words`);
4007
- }
4008
- }
4009
- return {
4010
- passed: violations.length === 0,
4011
- violations
4012
- };
4013
- }
4014
- /**
4015
- * Ensure knowledge base is loaded.
4016
- */
4017
- ensureLoaded() {
4018
- if (!this.loaded) {
4019
- this.data = this.getDefaultData();
4020
- this.loaded = true;
4021
- }
4022
- }
4023
- /**
4024
- * Get default data if YAML can't be loaded.
4025
- */
4026
- getDefaultData() {
4027
- return {
4028
- version: "1.0.0-fallback",
4029
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
4030
- experts: {
4031
- "Nancy Duarte": {
4032
- name: "Nancy Duarte",
4033
- principles: [
4034
- { name: "Glance Test", description: "Message clear in 3 seconds" },
4035
- { name: "STAR Moment", description: "Something They'll Always Remember" },
4036
- { name: "Sparkline", description: "Contrast What Is vs What Could Be" }
4037
- ]
4038
- },
4039
- "Garr Reynolds": {
4040
- name: "Garr Reynolds",
4041
- principles: [
4042
- { name: "Signal-to-Noise", description: "Maximize signal, minimize noise" },
4043
- { name: "Simplicity", description: "Amplify through simplification" }
4044
- ]
4045
- },
4046
- "Carmine Gallo": {
4047
- name: "Carmine Gallo",
4048
- principles: [
4049
- { name: "Rule of Three", description: "Maximum 3 key messages" },
4050
- { name: "Emotional Connection", description: "Connect emotionally first" }
4051
- ]
4052
- },
4053
- "Chris Anderson": {
4054
- name: "Chris Anderson",
4055
- principles: [
4056
- { name: "One Idea", description: "One powerful idea per talk" },
4057
- { name: "Dead Laptop Test", description: "Present without slides" }
4058
- ]
4059
- }
4060
- },
4061
- frameworkSelector: {
4062
- byAudience: {
4063
- "board": {
4064
- primaryFramework: "Barbara Minto",
4065
- slideTypes: ["executive_summary", "data_insight"]
4066
- },
4067
- "sales": {
4068
- primaryFramework: "Nancy Duarte",
4069
- slideTypes: ["big_idea", "social_proof"]
4070
- }
4071
- },
4072
- byGoal: {
4073
- "persuade": {
4074
- primaryFramework: "Nancy Duarte",
4075
- slideTypes: ["big_idea", "star_moment"]
4076
- },
4077
- "inform": {
4078
- primaryFramework: "Barbara Minto",
4079
- slideTypes: ["bullet_points", "data_insight"]
4080
- }
4081
- }
4082
- },
4083
- automatedQA: {
4084
- scoringRubric: {
4085
- totalPoints: 100,
4086
- passingThreshold: 95,
4087
- categories: {
4088
- visual: { weight: 35, checks: {} },
4089
- content: { weight: 30, checks: {} },
4090
- expert: { weight: 25, checks: {} },
4091
- accessibility: { weight: 10, checks: {} }
4092
- }
4093
- }
4094
- },
4095
- slideTypes: {},
4096
- modes: {
4097
- keynote: { maxWords: 25, minWhitespace: 35 },
4098
- business: { maxWords: 80, minWhitespace: 25 }
4099
- }
4100
- };
4101
- }
4102
- };
4103
- var knowledgeBaseInstance = null;
4104
- function getKnowledgeBase() {
4105
- if (!knowledgeBaseInstance) {
4106
- knowledgeBaseInstance = new KnowledgeBase();
4107
- }
4108
- return knowledgeBaseInstance;
4109
- }
4110
-
4111
4325
  // src/index.ts
4112
4326
  async function generate(config) {
4113
4327
  const engine = new PresentationEngine();
@@ -4122,7 +4336,7 @@ async function validate(presentation, options) {
4122
4336
  score
4123
4337
  };
4124
4338
  }
4125
- var VERSION = "1.0.0";
4339
+ var VERSION = "6.0.0";
4126
4340
  var index_default = {
4127
4341
  generate,
4128
4342
  validate,
@@ -4135,7 +4349,7 @@ export {
4135
4349
  CompositeChartProvider,
4136
4350
  CompositeImageProvider,
4137
4351
  ContentAnalyzer,
4138
- KnowledgeBase,
4352
+ KnowledgeGateway,
4139
4353
  LocalImageProvider,
4140
4354
  MermaidProvider,
4141
4355
  PlaceholderImageProvider,
@@ -4156,7 +4370,7 @@ export {
4156
4370
  createDefaultImageProvider,
4157
4371
  index_default as default,
4158
4372
  generate,
4159
- getKnowledgeBase,
4373
+ getKnowledgeGateway,
4160
4374
  validate
4161
4375
  };
4162
4376
  /**