claude-presentation-master 4.4.1 → 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
+ }
195
467
  }
196
468
  }
197
- return values.join("\n");
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;
475
+ }
476
+ }
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;
210
511
  }
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));
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;
535
+ }
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"]
@@ -804,14 +1082,22 @@ var SlideFactory = class {
804
1082
  return templates;
805
1083
  }
806
1084
  // === Helper Methods ===
1085
+ /**
1086
+ * Clean text by removing all content markers.
1087
+ */
1088
+ cleanText(text) {
1089
+ if (!text) return "";
1090
+ return text.replace(/\[HEADER\]\s*/g, "").replace(/\[BULLET\]\s*/g, "").replace(/\[NUMBERED\]\s*/g, "").replace(/\[EMPHASIS\]/g, "").replace(/\[\/EMPHASIS\]/g, "").replace(/\[CODE BLOCK\]/g, "").replace(/\[IMAGE\]/g, "").replace(/\s+/g, " ").trim();
1091
+ }
807
1092
  /**
808
1093
  * Truncate text to max length at word boundary.
809
1094
  */
810
1095
  truncate(text, maxLength) {
811
- if (!text || text.length <= maxLength) {
812
- return text ?? "";
1096
+ const cleanedText = this.cleanText(text);
1097
+ if (!cleanedText || cleanedText.length <= maxLength) {
1098
+ return cleanedText;
813
1099
  }
814
- const truncated = text.slice(0, maxLength);
1100
+ const truncated = cleanedText.slice(0, maxLength);
815
1101
  const lastSpace = truncated.lastIndexOf(" ");
816
1102
  if (lastSpace > maxLength * 0.7) {
817
1103
  return truncated.slice(0, lastSpace) + "...";
@@ -822,11 +1108,12 @@ var SlideFactory = class {
822
1108
  * Extract an action title from a message.
823
1109
  */
824
1110
  extractActionTitle(message) {
825
- const firstSentence = message.split(/[.!?]/)[0];
1111
+ const cleanedMessage = this.cleanText(message);
1112
+ const firstSentence = cleanedMessage.split(/[.!?]/)[0];
826
1113
  if (firstSentence && firstSentence.length <= 50) {
827
1114
  return firstSentence;
828
1115
  }
829
- const words = message.split(/\s+/).slice(0, 6);
1116
+ const words = cleanedMessage.split(/\s+/).slice(0, 6);
830
1117
  return words.join(" ");
831
1118
  }
832
1119
  /**
@@ -836,9 +1123,10 @@ var SlideFactory = class {
836
1123
  if (!text) return [];
837
1124
  const bulletMatches = text.match(/\[BULLET\]\s*(.+)/g);
838
1125
  if (bulletMatches && bulletMatches.length > 0) {
839
- return bulletMatches.map((b) => b.replace("[BULLET]", "").trim()).slice(0, 5);
1126
+ return bulletMatches.map((b) => this.cleanText(b)).slice(0, 5);
840
1127
  }
841
- const sentences = text.split(/[.!?]+/).filter((s) => s.trim().length > 10);
1128
+ const cleanedText = this.cleanText(text);
1129
+ const sentences = cleanedText.split(/[.!?]+/).filter((s) => s.trim().length > 10);
842
1130
  return sentences.slice(0, 5).map((s) => s.trim());
843
1131
  }
844
1132
  /**
@@ -1745,19 +2033,28 @@ var ScoreCalculator = class {
1745
2033
  import { chromium } from "playwright";
1746
2034
  var QAEngine = class {
1747
2035
  browser = null;
2036
+ kb;
1748
2037
  /**
1749
- * Validate a presentation.
2038
+ * Initialize KnowledgeGateway
2039
+ */
2040
+ async initialize() {
2041
+ this.kb = await getKnowledgeGateway();
2042
+ }
2043
+ /**
2044
+ * Validate a presentation using KB-driven quality metrics
1750
2045
  */
1751
2046
  async validate(presentation, options) {
2047
+ await this.initialize();
1752
2048
  const html = typeof presentation === "string" ? presentation : presentation.toString("utf-8");
1753
2049
  const mode = options?.mode ?? "keynote";
2050
+ const qualityMetrics = this.kb.getQualityMetrics();
1754
2051
  await this.initBrowser();
1755
2052
  try {
1756
2053
  const [visualResults, contentResults, expertResults, accessibilityResults] = await Promise.all([
1757
- this.runVisualTests(html, mode),
1758
- this.runContentTests(html, mode),
2054
+ this.runVisualTests(html, mode, qualityMetrics),
2055
+ this.runContentTests(html, mode, qualityMetrics),
1759
2056
  this.runExpertTests(html, mode),
1760
- this.runAccessibilityTests(html)
2057
+ this.runAccessibilityTests(html, qualityMetrics)
1761
2058
  ]);
1762
2059
  const issues = this.collectIssues(visualResults, contentResults, expertResults, accessibilityResults);
1763
2060
  const errorCount = issues.filter((i) => i.severity === "error").length;
@@ -1775,18 +2072,18 @@ var QAEngine = class {
1775
2072
  }
1776
2073
  }
1777
2074
  /**
1778
- * Calculate overall QA score.
2075
+ * Calculate overall QA score using KB-driven weights
1779
2076
  */
1780
2077
  calculateScore(results) {
1781
2078
  const weights = {
1782
2079
  visual: 0.35,
1783
- // 35%
2080
+ // 35% - Design quality
1784
2081
  content: 0.3,
1785
- // 30%
2082
+ // 30% - Content quality
1786
2083
  expert: 0.25,
1787
- // 25%
2084
+ // 25% - Expert methodology
1788
2085
  accessibility: 0.1
1789
- // 10%
2086
+ // 10% - Accessibility
1790
2087
  };
1791
2088
  const visualScore = this.calculateVisualScore(results.visual);
1792
2089
  const contentScore = this.calculateContentScore(results.content);
@@ -1833,13 +2130,15 @@ var QAEngine = class {
1833
2130
  };
1834
2131
  }
1835
2132
  // ===========================================================================
1836
- // VISUAL TESTS
2133
+ // VISUAL TESTS - KB-DRIVEN
1837
2134
  // ===========================================================================
1838
- async runVisualTests(html, mode) {
2135
+ async runVisualTests(html, mode, qualityMetrics) {
1839
2136
  const page = await this.browser.newPage();
1840
2137
  await page.setViewportSize({ width: 1280, height: 720 });
1841
2138
  await page.setContent(html);
1842
2139
  await page.waitForTimeout(1e3);
2140
+ const modeMetrics = mode === "keynote" ? qualityMetrics.keynote_mode : qualityMetrics.business_mode;
2141
+ const minWhitespace = this.kb.getWhitespaceRequirement(mode) * 100;
1843
2142
  const slideCount = await page.evaluate(() => {
1844
2143
  return window.Reveal?.getTotalSlides?.() ?? document.querySelectorAll(".slides > section").length;
1845
2144
  });
@@ -1905,9 +2204,8 @@ var QAEngine = class {
1905
2204
  }, { slideIndex: i });
1906
2205
  if (slideAnalysis) {
1907
2206
  const issues = [];
1908
- const minWhitespace = mode === "keynote" ? 40 : 25;
1909
2207
  if (slideAnalysis.whitespace < minWhitespace) {
1910
- issues.push(`Whitespace ${slideAnalysis.whitespace}% below ${minWhitespace}% minimum`);
2208
+ issues.push(`Whitespace ${slideAnalysis.whitespace}% below ${minWhitespace}% minimum (KB-defined)`);
1911
2209
  }
1912
2210
  if (slideAnalysis.whitespace > 80) {
1913
2211
  issues.push(`Whitespace ${slideAnalysis.whitespace}% - slide appears sparse`);
@@ -1962,34 +2260,35 @@ var QAEngine = class {
1962
2260
  };
1963
2261
  }
1964
2262
  // ===========================================================================
1965
- // CONTENT TESTS
2263
+ // CONTENT TESTS - KB-DRIVEN
1966
2264
  // ===========================================================================
1967
- async runContentTests(html, mode) {
2265
+ async runContentTests(html, mode, qualityMetrics) {
1968
2266
  const page = await this.browser.newPage();
1969
2267
  await page.setContent(html);
1970
2268
  await page.waitForTimeout(500);
1971
- 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;
1972
2273
  const slides = document.querySelectorAll(".slides > section");
1973
2274
  const perSlide = [];
1974
- const glanceTest = [];
2275
+ const glanceTestResults = [];
1975
2276
  const signalNoise = [];
1976
2277
  const oneIdea = [];
1977
2278
  slides.forEach((slide, index) => {
1978
2279
  const text = slide.innerText || "";
1979
2280
  const words = text.split(/\s+/).filter((w) => w.length > 0);
1980
2281
  const wordCount = words.length;
1981
- const maxWords = targetMode === "keynote" ? 25 : 80;
1982
- const minWords = targetMode === "business" ? 20 : 0;
1983
2282
  const withinLimit = wordCount <= maxWords && wordCount >= minWords;
1984
2283
  const title = slide.querySelector("h2")?.textContent || "";
1985
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);
1986
2285
  const hasInsight = title.length > 30 && hasVerb;
1987
2286
  const issues = [];
1988
2287
  if (!withinLimit) {
1989
- issues.push(`Word count ${wordCount} outside ${minWords}-${maxWords} range`);
2288
+ issues.push(`Word count ${wordCount} outside ${minWords}-${maxWords} range (KB-defined)`);
1990
2289
  }
1991
2290
  if (targetMode === "business" && !hasInsight && index > 0) {
1992
- issues.push("Title is not action-oriented (missing insight)");
2291
+ issues.push("Title is not action-oriented (missing insight per Minto)");
1993
2292
  }
1994
2293
  perSlide.push({
1995
2294
  slideIndex: index,
@@ -2002,12 +2301,12 @@ var QAEngine = class {
2002
2301
  const keyMessage = prominentElement?.textContent?.trim() || "";
2003
2302
  const keyWordCount = keyMessage.split(/\s+/).filter((w) => w.length > 0).length;
2004
2303
  const readingTime = keyWordCount / 4.2;
2005
- glanceTest.push({
2304
+ glanceTestResults.push({
2006
2305
  slideIndex: index,
2007
2306
  keyMessage,
2008
2307
  wordCount: keyWordCount,
2009
2308
  readingTime: Math.round(readingTime * 10) / 10,
2010
- passed: readingTime <= 3 && keyWordCount <= 15,
2309
+ passed: readingTime <= 3 && keyWordCount <= glanceWordLimit,
2011
2310
  recommendation: readingTime > 3 ? `Shorten to ${Math.floor(3 * 4.2)} words or less` : void 0
2012
2311
  });
2013
2312
  const elements = slide.querySelectorAll("h1, h2, h3, p, li, img");
@@ -2034,35 +2333,143 @@ var QAEngine = class {
2034
2333
  conflictingIdeas: ideaCount > 2 ? ["Multiple competing ideas detected"] : void 0
2035
2334
  });
2036
2335
  });
2037
- return { perSlide, glanceTest, signalNoise, oneIdea };
2038
- }, 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
+ });
2039
2343
  await page.close();
2040
2344
  return results;
2041
2345
  }
2042
2346
  // ===========================================================================
2043
- // EXPERT TESTS
2347
+ // EXPERT TESTS - KB-DRIVEN
2044
2348
  // ===========================================================================
2045
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");
2354
+ return {
2355
+ duarte: this.validateDuartePrinciples(html, duartePrinciples),
2356
+ reynolds: this.validateReynoldsPrinciples(html, reynoldsPrinciples),
2357
+ gallo: this.validateGalloPrinciples(html, galloPrinciples),
2358
+ anderson: this.validateAndersonPrinciples(html, andersonPrinciples)
2359
+ };
2360
+ }
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
+ }
2046
2380
  return {
2047
- duarte: this.createExpertResult("Nancy Duarte", ["Glance Test", "STAR Moment", "Sparkline"], 85),
2048
- reynolds: this.createExpertResult("Garr Reynolds", ["Signal-to-Noise", "Simplicity", "Picture Superiority"], 80),
2049
- gallo: this.createExpertResult("Carmine Gallo", ["Rule of Three", "Emotional Connection"], 85),
2050
- anderson: this.createExpertResult("Chris Anderson", ["One Idea", "Clarity"], 90)
2381
+ expertName: "Nancy Duarte",
2382
+ principlesChecked: ["Glance Test", "STAR Moment", "Sparkline Structure"],
2383
+ passed: score >= 80,
2384
+ score: Math.max(0, score),
2385
+ violations
2051
2386
  };
2052
2387
  }
2053
- createExpertResult(name, principles, score) {
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
+ }
2054
2407
  return {
2055
- expertName: name,
2056
- principlesChecked: principles,
2408
+ expertName: "Garr Reynolds",
2409
+ principlesChecked: ["Signal-to-Noise", "Simplicity", "Picture Superiority"],
2057
2410
  passed: score >= 80,
2058
- score,
2059
- violations: score < 80 ? [`${name} principles not fully met`] : []
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
2060
2467
  };
2061
2468
  }
2062
2469
  // ===========================================================================
2063
- // ACCESSIBILITY TESTS
2470
+ // ACCESSIBILITY TESTS - KB-DRIVEN
2064
2471
  // ===========================================================================
2065
- async runAccessibilityTests(html) {
2472
+ async runAccessibilityTests(html, qualityMetrics) {
2066
2473
  const page = await this.browser.newPage();
2067
2474
  await page.setContent(html);
2068
2475
  await page.waitForTimeout(500);
@@ -2112,7 +2519,7 @@ var QAEngine = class {
2112
2519
  };
2113
2520
  }
2114
2521
  // ===========================================================================
2115
- // SCORING
2522
+ // SCORING - KB-DRIVEN
2116
2523
  // ===========================================================================
2117
2524
  calculateVisualScore(results) {
2118
2525
  let score = 100;
@@ -2236,6 +2643,7 @@ var QAEngine = class {
2236
2643
  // src/generators/html/RevealJsGenerator.ts
2237
2644
  var RevealJsGenerator = class {
2238
2645
  templateEngine;
2646
+ kb;
2239
2647
  defaultRevealConfig = {
2240
2648
  revealVersion: "5.0.4",
2241
2649
  hash: true,
@@ -2254,20 +2662,32 @@ var RevealJsGenerator = class {
2254
2662
  constructor() {
2255
2663
  this.templateEngine = new TemplateEngine();
2256
2664
  }
2665
+ /**
2666
+ * Initialize KnowledgeGateway
2667
+ */
2668
+ async initialize() {
2669
+ this.kb = await getKnowledgeGateway();
2670
+ }
2257
2671
  /**
2258
2672
  * Generate complete Reveal.js HTML presentation.
2259
2673
  */
2260
2674
  async generate(slides, config) {
2675
+ await this.initialize();
2261
2676
  const templateConfig = {};
2262
2677
  if (config.theme) templateConfig.theme = config.theme;
2263
2678
  if (config.customTemplates) templateConfig.customTemplates = config.customTemplates;
2264
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);
2265
2683
  const docConfig = {
2266
2684
  title: config.title,
2267
2685
  slides: slideHtml.join("\n"),
2268
2686
  theme: config.theme ?? "default",
2269
2687
  revealConfig: this.defaultRevealConfig,
2270
- mode: config.mode
2688
+ mode: config.mode,
2689
+ palette,
2690
+ typography
2271
2691
  };
2272
2692
  if (config.author) docConfig.author = config.author;
2273
2693
  if (config.subject) docConfig.subject = config.subject;
@@ -2278,11 +2698,23 @@ var RevealJsGenerator = class {
2278
2698
  }
2279
2699
  return html;
2280
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
+ }
2281
2713
  /**
2282
2714
  * Build the complete HTML document.
2283
2715
  */
2284
2716
  buildDocument(options) {
2285
- const { title, author, subject, slides, theme, customCSS, revealConfig, mode } = options;
2717
+ const { title, author, subject, slides, theme, customCSS, revealConfig, mode, palette, typography } = options;
2286
2718
  return `<!DOCTYPE html>
2287
2719
  <html lang="en">
2288
2720
  <head>
@@ -2290,7 +2722,7 @@ var RevealJsGenerator = class {
2290
2722
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
2291
2723
  <meta name="author" content="${this.escapeHtml(author ?? "Claude Presentation Master")}">
2292
2724
  <meta name="description" content="${this.escapeHtml(subject ?? "")}">
2293
- <meta name="generator" content="Claude Presentation Master v1.0.0">
2725
+ <meta name="generator" content="Claude Presentation Master v6.0.0">
2294
2726
  <title>${this.escapeHtml(title)}</title>
2295
2727
 
2296
2728
  <!-- Reveal.js CSS -->
@@ -2304,10 +2736,10 @@ var RevealJsGenerator = class {
2304
2736
  <!-- Mermaid for diagrams -->
2305
2737
  <script src="https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js"></script>
2306
2738
 
2307
- <!-- Presentation Engine CSS -->
2739
+ <!-- Presentation Engine CSS - KB-Driven Design System -->
2308
2740
  <style>
2309
- ${this.getBaseStyles(mode)}
2310
- ${this.getThemeStyles(theme)}
2741
+ ${this.getBaseStyles(mode, palette, typography)}
2742
+ ${this.getThemeStyles(theme, palette)}
2311
2743
  ${this.getAnimationStyles()}
2312
2744
  ${customCSS ?? ""}
2313
2745
  </style>
@@ -2359,26 +2791,35 @@ ${slides}
2359
2791
  </html>`;
2360
2792
  }
2361
2793
  /**
2362
- * Get base styles for slides.
2794
+ * Get base styles for slides - KB-DRIVEN
2363
2795
  */
2364
- getBaseStyles(mode) {
2796
+ getBaseStyles(mode, palette, typography) {
2365
2797
  const fontSize = mode === "keynote" ? "2.5em" : "1.8em";
2366
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";
2367
2805
  return `
2368
- /* Base Styles */
2806
+ /* Base Styles - KB-Driven Design System */
2369
2807
  :root {
2808
+ /* Typography from KB */
2370
2809
  --font-heading: 'Source Sans Pro', 'Helvetica Neue', -apple-system, BlinkMacSystemFont, sans-serif;
2371
2810
  --font-body: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
2372
2811
  --font-mono: 'SF Mono', 'Fira Code', 'JetBrains Mono', monospace;
2373
2812
 
2374
- --color-primary: #1a1a2e;
2375
- --color-secondary: #16213e;
2376
- --color-accent: #0f3460;
2377
- --color-highlight: #e94560;
2378
- --color-text: #1a1a2e;
2379
- --color-text-light: #4a4a68;
2380
- --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};
2381
2821
 
2822
+ /* Layout */
2382
2823
  --slide-padding: 60px;
2383
2824
  --content-max-width: 1200px;
2384
2825
  }
@@ -2494,7 +2935,7 @@ ${slides}
2494
2935
  font-size: 0.8em;
2495
2936
  }
2496
2937
 
2497
- /* Images */
2938
+ /* Images - NO RANDOM PICSUM - Only user-provided or none */
2498
2939
  .reveal img {
2499
2940
  max-width: 100%;
2500
2941
  height: auto;
@@ -2600,9 +3041,25 @@ ${slides}
2600
3041
  `;
2601
3042
  }
2602
3043
  /**
2603
- * 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
2604
3058
  */
2605
- getThemeStyles(theme) {
3059
+ getThemeStyles(theme, palette) {
3060
+ if (theme === "default") {
3061
+ return "";
3062
+ }
2606
3063
  const themes = {
2607
3064
  "default": "",
2608
3065
  "light-corporate": `
@@ -2616,10 +3073,10 @@ ${slides}
2616
3073
  `,
2617
3074
  "modern-tech": `
2618
3075
  :root {
2619
- --color-primary: #1a1a2e;
2620
- --color-secondary: #16213e;
2621
- --color-accent: #0f3460;
2622
- --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"};
2623
3080
  --color-background: #f8f9fa;
2624
3081
  }
2625
3082
  `,
@@ -3865,239 +4322,6 @@ function createDefaultImageProvider(options) {
3865
4322
  return new CompositeImageProvider(providers);
3866
4323
  }
3867
4324
 
3868
- // src/knowledge/KnowledgeBase.ts
3869
- import { readFileSync } from "fs";
3870
- import { join, dirname } from "path";
3871
- import { fileURLToPath } from "url";
3872
- import * as yaml from "yaml";
3873
- function getModuleDir() {
3874
- if (typeof import.meta !== "undefined" && import.meta.url) {
3875
- return dirname(fileURLToPath(import.meta.url));
3876
- }
3877
- if (typeof __dirname !== "undefined") {
3878
- return __dirname;
3879
- }
3880
- return process.cwd();
3881
- }
3882
- var moduleDir = getModuleDir();
3883
- var KnowledgeBase = class {
3884
- data = null;
3885
- loaded = false;
3886
- /**
3887
- * Load the knowledge base from the bundled YAML file.
3888
- */
3889
- async load() {
3890
- if (this.loaded) return;
3891
- try {
3892
- const possiblePaths = [
3893
- // From dist/: go up to package root
3894
- join(moduleDir, "../../assets/presentation-knowledge.yaml"),
3895
- join(moduleDir, "../assets/presentation-knowledge.yaml"),
3896
- join(moduleDir, "../../../assets/presentation-knowledge.yaml"),
3897
- // From bundle in dist/index.js: go up one level
3898
- join(moduleDir, "assets/presentation-knowledge.yaml"),
3899
- // CWD-based paths for development or local installs
3900
- join(process.cwd(), "assets/presentation-knowledge.yaml"),
3901
- join(process.cwd(), "node_modules/claude-presentation-master/assets/presentation-knowledge.yaml")
3902
- ];
3903
- let assetPath = "";
3904
- for (const p of possiblePaths) {
3905
- try {
3906
- readFileSync(p);
3907
- assetPath = p;
3908
- break;
3909
- } catch {
3910
- continue;
3911
- }
3912
- }
3913
- if (!assetPath) {
3914
- throw new Error("Could not locate knowledge base file");
3915
- }
3916
- const content = readFileSync(assetPath, "utf-8");
3917
- this.data = yaml.parse(content);
3918
- this.loaded = true;
3919
- console.log(`\u{1F4DA} Knowledge base loaded: v${this.data.version}`);
3920
- } catch (error) {
3921
- console.warn("\u26A0\uFE0F Could not load knowledge base, using defaults");
3922
- this.data = this.getDefaultData();
3923
- this.loaded = true;
3924
- }
3925
- }
3926
- /**
3927
- * Get expert methodology by name.
3928
- */
3929
- getExpert(name) {
3930
- this.ensureLoaded();
3931
- return this.data?.experts?.[name];
3932
- }
3933
- /**
3934
- * Get all expert names.
3935
- */
3936
- getExpertNames() {
3937
- this.ensureLoaded();
3938
- return Object.keys(this.data?.experts ?? {});
3939
- }
3940
- /**
3941
- * Get framework recommendation for audience.
3942
- */
3943
- getFrameworkForAudience(audience) {
3944
- this.ensureLoaded();
3945
- return this.data?.frameworkSelector?.byAudience?.[audience];
3946
- }
3947
- /**
3948
- * Get framework recommendation for goal.
3949
- */
3950
- getFrameworkForGoal(goal) {
3951
- this.ensureLoaded();
3952
- return this.data?.frameworkSelector?.byGoal?.[goal];
3953
- }
3954
- /**
3955
- * Get QA scoring rubric.
3956
- */
3957
- getScoringRubric() {
3958
- this.ensureLoaded();
3959
- return this.data?.automatedQA?.scoringRubric;
3960
- }
3961
- /**
3962
- * Get mode configuration (keynote or business).
3963
- */
3964
- getModeConfig(mode) {
3965
- this.ensureLoaded();
3966
- return this.data?.modes?.[mode];
3967
- }
3968
- /**
3969
- * Get slide type configuration.
3970
- */
3971
- getSlideType(type) {
3972
- this.ensureLoaded();
3973
- return this.data?.slideTypes?.[type];
3974
- }
3975
- /**
3976
- * Get the knowledge base version.
3977
- */
3978
- getVersion() {
3979
- this.ensureLoaded();
3980
- return this.data?.version ?? "unknown";
3981
- }
3982
- /**
3983
- * Validate a slide against expert principles.
3984
- */
3985
- validateAgainstExpert(expertName, slideData) {
3986
- const expert = this.getExpert(expertName);
3987
- if (!expert) {
3988
- return { passed: true, violations: [] };
3989
- }
3990
- const violations = [];
3991
- if (expert.wordLimits) {
3992
- if (expert.wordLimits.max && slideData.wordCount > expert.wordLimits.max) {
3993
- violations.push(`Exceeds ${expertName} word limit of ${expert.wordLimits.max}`);
3994
- }
3995
- if (expert.wordLimits.min && slideData.wordCount < expert.wordLimits.min) {
3996
- violations.push(`Below ${expertName} minimum of ${expert.wordLimits.min} words`);
3997
- }
3998
- }
3999
- return {
4000
- passed: violations.length === 0,
4001
- violations
4002
- };
4003
- }
4004
- /**
4005
- * Ensure knowledge base is loaded.
4006
- */
4007
- ensureLoaded() {
4008
- if (!this.loaded) {
4009
- this.data = this.getDefaultData();
4010
- this.loaded = true;
4011
- }
4012
- }
4013
- /**
4014
- * Get default data if YAML can't be loaded.
4015
- */
4016
- getDefaultData() {
4017
- return {
4018
- version: "1.0.0-fallback",
4019
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
4020
- experts: {
4021
- "Nancy Duarte": {
4022
- name: "Nancy Duarte",
4023
- principles: [
4024
- { name: "Glance Test", description: "Message clear in 3 seconds" },
4025
- { name: "STAR Moment", description: "Something They'll Always Remember" },
4026
- { name: "Sparkline", description: "Contrast What Is vs What Could Be" }
4027
- ]
4028
- },
4029
- "Garr Reynolds": {
4030
- name: "Garr Reynolds",
4031
- principles: [
4032
- { name: "Signal-to-Noise", description: "Maximize signal, minimize noise" },
4033
- { name: "Simplicity", description: "Amplify through simplification" }
4034
- ]
4035
- },
4036
- "Carmine Gallo": {
4037
- name: "Carmine Gallo",
4038
- principles: [
4039
- { name: "Rule of Three", description: "Maximum 3 key messages" },
4040
- { name: "Emotional Connection", description: "Connect emotionally first" }
4041
- ]
4042
- },
4043
- "Chris Anderson": {
4044
- name: "Chris Anderson",
4045
- principles: [
4046
- { name: "One Idea", description: "One powerful idea per talk" },
4047
- { name: "Dead Laptop Test", description: "Present without slides" }
4048
- ]
4049
- }
4050
- },
4051
- frameworkSelector: {
4052
- byAudience: {
4053
- "board": {
4054
- primaryFramework: "Barbara Minto",
4055
- slideTypes: ["executive_summary", "data_insight"]
4056
- },
4057
- "sales": {
4058
- primaryFramework: "Nancy Duarte",
4059
- slideTypes: ["big_idea", "social_proof"]
4060
- }
4061
- },
4062
- byGoal: {
4063
- "persuade": {
4064
- primaryFramework: "Nancy Duarte",
4065
- slideTypes: ["big_idea", "star_moment"]
4066
- },
4067
- "inform": {
4068
- primaryFramework: "Barbara Minto",
4069
- slideTypes: ["bullet_points", "data_insight"]
4070
- }
4071
- }
4072
- },
4073
- automatedQA: {
4074
- scoringRubric: {
4075
- totalPoints: 100,
4076
- passingThreshold: 95,
4077
- categories: {
4078
- visual: { weight: 35, checks: {} },
4079
- content: { weight: 30, checks: {} },
4080
- expert: { weight: 25, checks: {} },
4081
- accessibility: { weight: 10, checks: {} }
4082
- }
4083
- }
4084
- },
4085
- slideTypes: {},
4086
- modes: {
4087
- keynote: { maxWords: 25, minWhitespace: 35 },
4088
- business: { maxWords: 80, minWhitespace: 25 }
4089
- }
4090
- };
4091
- }
4092
- };
4093
- var knowledgeBaseInstance = null;
4094
- function getKnowledgeBase() {
4095
- if (!knowledgeBaseInstance) {
4096
- knowledgeBaseInstance = new KnowledgeBase();
4097
- }
4098
- return knowledgeBaseInstance;
4099
- }
4100
-
4101
4325
  // src/index.ts
4102
4326
  async function generate(config) {
4103
4327
  const engine = new PresentationEngine();
@@ -4112,7 +4336,7 @@ async function validate(presentation, options) {
4112
4336
  score
4113
4337
  };
4114
4338
  }
4115
- var VERSION = "1.0.0";
4339
+ var VERSION = "6.0.0";
4116
4340
  var index_default = {
4117
4341
  generate,
4118
4342
  validate,
@@ -4125,7 +4349,7 @@ export {
4125
4349
  CompositeChartProvider,
4126
4350
  CompositeImageProvider,
4127
4351
  ContentAnalyzer,
4128
- KnowledgeBase,
4352
+ KnowledgeGateway,
4129
4353
  LocalImageProvider,
4130
4354
  MermaidProvider,
4131
4355
  PlaceholderImageProvider,
@@ -4146,7 +4370,7 @@ export {
4146
4370
  createDefaultImageProvider,
4147
4371
  index_default as default,
4148
4372
  generate,
4149
- getKnowledgeBase,
4373
+ getKnowledgeGateway,
4150
4374
  validate
4151
4375
  };
4152
4376
  /**