sonance-brand-mcp 1.1.3 → 1.1.7

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.
Files changed (71) hide show
  1. package/dist/assets/components/accordion.stories.tsx +310 -0
  2. package/dist/assets/components/accordion.tsx +56 -30
  3. package/dist/assets/components/alert.stories.tsx +199 -0
  4. package/dist/assets/components/autocomplete.stories.tsx +307 -0
  5. package/dist/assets/components/autocomplete.tsx +28 -4
  6. package/dist/assets/components/avatar.stories.tsx +175 -0
  7. package/dist/assets/components/badge.stories.tsx +258 -0
  8. package/dist/assets/components/breadcrumbs.stories.tsx +175 -0
  9. package/dist/assets/components/button.stories.tsx +362 -0
  10. package/dist/assets/components/button.tsx +48 -3
  11. package/dist/assets/components/calendar.stories.tsx +247 -0
  12. package/dist/assets/components/card.stories.tsx +275 -0
  13. package/dist/assets/components/card.tsx +26 -1
  14. package/dist/assets/components/checkbox-group.stories.tsx +281 -0
  15. package/dist/assets/components/checkbox.stories.tsx +160 -0
  16. package/dist/assets/components/checkbox.tsx +32 -4
  17. package/dist/assets/components/code.stories.tsx +265 -0
  18. package/dist/assets/components/date-input.stories.tsx +278 -0
  19. package/dist/assets/components/date-input.tsx +24 -2
  20. package/dist/assets/components/date-picker.stories.tsx +337 -0
  21. package/dist/assets/components/date-picker.tsx +28 -4
  22. package/dist/assets/components/date-range-picker.stories.tsx +340 -0
  23. package/dist/assets/components/dialog.stories.tsx +285 -0
  24. package/dist/assets/components/divider.stories.tsx +176 -0
  25. package/dist/assets/components/drawer.stories.tsx +216 -0
  26. package/dist/assets/components/dropdown.stories.tsx +342 -0
  27. package/dist/assets/components/dropdown.tsx +55 -10
  28. package/dist/assets/components/form.stories.tsx +372 -0
  29. package/dist/assets/components/image.stories.tsx +348 -0
  30. package/dist/assets/components/input-otp.stories.tsx +336 -0
  31. package/dist/assets/components/input-otp.tsx +24 -2
  32. package/dist/assets/components/input.stories.tsx +223 -0
  33. package/dist/assets/components/input.tsx +27 -2
  34. package/dist/assets/components/kbd.stories.tsx +272 -0
  35. package/dist/assets/components/link.stories.tsx +199 -0
  36. package/dist/assets/components/link.tsx +50 -1
  37. package/dist/assets/components/listbox.stories.tsx +287 -0
  38. package/dist/assets/components/listbox.tsx +30 -7
  39. package/dist/assets/components/navbar.stories.tsx +218 -0
  40. package/dist/assets/components/number-input.stories.tsx +295 -0
  41. package/dist/assets/components/number-input.tsx +30 -8
  42. package/dist/assets/components/pagination.stories.tsx +280 -0
  43. package/dist/assets/components/pagination.tsx +45 -21
  44. package/dist/assets/components/popover.stories.tsx +219 -0
  45. package/dist/assets/components/progress.stories.tsx +153 -0
  46. package/dist/assets/components/radio-group.stories.tsx +187 -0
  47. package/dist/assets/components/radio-group.tsx +30 -6
  48. package/dist/assets/components/range-calendar.stories.tsx +334 -0
  49. package/dist/assets/components/scroll-shadow.stories.tsx +335 -0
  50. package/dist/assets/components/select.stories.tsx +192 -0
  51. package/dist/assets/components/select.tsx +54 -7
  52. package/dist/assets/components/skeleton.stories.tsx +166 -0
  53. package/dist/assets/components/slider.stories.tsx +145 -0
  54. package/dist/assets/components/slider.tsx +43 -8
  55. package/dist/assets/components/spacer.stories.tsx +216 -0
  56. package/dist/assets/components/spinner.stories.tsx +149 -0
  57. package/dist/assets/components/switch.stories.tsx +170 -0
  58. package/dist/assets/components/switch.tsx +29 -4
  59. package/dist/assets/components/table.stories.tsx +322 -0
  60. package/dist/assets/components/tabs.stories.tsx +306 -0
  61. package/dist/assets/components/tabs.tsx +25 -4
  62. package/dist/assets/components/textarea.stories.tsx +103 -0
  63. package/dist/assets/components/textarea.tsx +27 -3
  64. package/dist/assets/components/theme-toggle.stories.tsx +248 -0
  65. package/dist/assets/components/time-input.stories.tsx +365 -0
  66. package/dist/assets/components/time-input.tsx +25 -3
  67. package/dist/assets/components/toast.stories.tsx +195 -0
  68. package/dist/assets/components/tooltip.stories.tsx +226 -0
  69. package/dist/assets/components/user.stories.tsx +274 -0
  70. package/dist/index.js +1732 -13
  71. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -181,6 +181,89 @@ if (process.argv.includes("--init-code") || process.argv.includes("init-code"))
181
181
  runClaudeCodeInstaller();
182
182
  process.exit(0);
183
183
  }
184
+ /**
185
+ * Run the installer for Cursor IDE (creates .cursor/mcp.json in current directory)
186
+ */
187
+ function runCursorInstaller() {
188
+ console.log("");
189
+ console.log(" ┌─────────────────────────────────────────────────┐");
190
+ console.log(" │ │");
191
+ console.log(" │ 🎨 Sonance Brand MCP - Cursor IDE Setup │");
192
+ console.log(" │ │");
193
+ console.log(" └─────────────────────────────────────────────────┘");
194
+ console.log("");
195
+ const cursorDir = path.join(process.cwd(), ".cursor");
196
+ const configPath = path.join(cursorDir, "mcp.json");
197
+ console.log(` 📍 Config file: ${configPath}`);
198
+ console.log("");
199
+ // Create .cursor directory if it doesn't exist
200
+ if (!fs.existsSync(cursorDir)) {
201
+ console.log(" 📁 Creating .cursor directory...");
202
+ fs.mkdirSync(cursorDir, { recursive: true });
203
+ }
204
+ // Read existing config or create new one
205
+ let config = {};
206
+ if (fs.existsSync(configPath)) {
207
+ try {
208
+ const content = fs.readFileSync(configPath, "utf-8");
209
+ config = JSON.parse(content);
210
+ console.log(" 📄 Found existing .cursor/mcp.json");
211
+ }
212
+ catch {
213
+ console.log(" ⚠️ Could not parse existing mcp.json, creating new one");
214
+ }
215
+ }
216
+ else {
217
+ console.log(" 📄 Creating new .cursor/mcp.json");
218
+ }
219
+ // Initialize mcpServers if not present
220
+ if (!config.mcpServers) {
221
+ config.mcpServers = {};
222
+ }
223
+ // Check if already installed
224
+ if (config.mcpServers["sonance-brand"]) {
225
+ console.log(" ✅ sonance-brand is already configured!");
226
+ console.log("");
227
+ console.log(" To use it:");
228
+ console.log(" 1. Restart Cursor (Cmd+Q / Alt+F4, then reopen)");
229
+ console.log(" 2. Open this project in Cursor");
230
+ console.log(" 3. The MCP tools will be available in Agent mode");
231
+ console.log("");
232
+ return;
233
+ }
234
+ // Add Sonance server config (using npx for auto-updates)
235
+ config.mcpServers["sonance-brand"] = {
236
+ command: "npx",
237
+ args: ["-y", "sonance-brand-mcp"],
238
+ };
239
+ // Write updated config
240
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
241
+ console.log("");
242
+ console.log(" ✅ Sonance Brand MCP installed successfully!");
243
+ console.log("");
244
+ console.log(" ┌─────────────────────────────────────────────────┐");
245
+ console.log(" │ Next steps: │");
246
+ console.log(" │ │");
247
+ console.log(" │ 1. Restart Cursor │");
248
+ console.log(" │ • Mac: Press Cmd+Q, then reopen │");
249
+ console.log(" │ • Windows: Press Alt+F4, then reopen │");
250
+ console.log(" │ │");
251
+ console.log(" │ 2. Open this project in Cursor │");
252
+ console.log(" │ │");
253
+ console.log(" │ 3. Use Agent mode (not Ask mode) to access │");
254
+ console.log(" │ MCP tools │");
255
+ console.log(" │ │");
256
+ console.log(" └─────────────────────────────────────────────────┘");
257
+ console.log("");
258
+ console.log(" Try asking Cursor:");
259
+ console.log(" \"What are the official Sonance brand colors?\"");
260
+ console.log("");
261
+ }
262
+ // Check for --init-cursor flag BEFORE starting MCP server
263
+ if (process.argv.includes("--init-cursor") || process.argv.includes("init-cursor")) {
264
+ runCursorInstaller();
265
+ process.exit(0);
266
+ }
184
267
  // ============================================
185
268
  // PATH RESOLUTION
186
269
  // ============================================
@@ -223,6 +306,189 @@ function getAssetPath(assetType) {
223
306
  }
224
307
  }
225
308
  }
309
+ /**
310
+ * Detect output type from user's request/prompt
311
+ */
312
+ function detectFromRequest(prompt) {
313
+ const lowerPrompt = prompt.toLowerCase();
314
+ // Document indicators
315
+ if (/word|docx|pdf|proposal|spec sheet|report|spreadsheet|excel|xlsx/.test(lowerPrompt))
316
+ return "doc";
317
+ // Marketing indicators
318
+ if (/email|newsletter|banner|landing page|campaign|advertisement|promo/.test(lowerPrompt))
319
+ return "marketing";
320
+ // Technical doc indicators
321
+ if (/readme|documentation|api doc|guide|changelog|\.md\b/.test(lowerPrompt))
322
+ return "tech";
323
+ // UI indicators
324
+ if (/component|button|card|form|modal|dashboard|app|page|ui|react|next\.?js/.test(lowerPrompt))
325
+ return "ui";
326
+ return null;
327
+ }
328
+ /**
329
+ * Detect output type from code patterns
330
+ */
331
+ function detectFromCode(code) {
332
+ // Document generation libraries
333
+ if (/from docx|python-docx|Document\(|reportlab|openpyxl|Workbook\(|docx\.|\.docx|PDFDocument/.test(code))
334
+ return "doc";
335
+ // Marketing patterns (email-safe HTML)
336
+ if (/email-safe|mso-|outlook|table.*cellpadding|inline.*style.*color|<!--\[if mso\]/i.test(code))
337
+ return "marketing";
338
+ // Technical documentation (Markdown)
339
+ if (/^#{1,3}\s|```[\w]*\n|README|\.md"|frontmatter|---\n.*title:/m.test(code))
340
+ return "tech";
341
+ // UI/React patterns (default)
342
+ return "ui";
343
+ }
344
+ /**
345
+ * Smart detection: tries request first, then code
346
+ */
347
+ function smartDetectOutputType(prompt, code) {
348
+ const fromPrompt = detectFromRequest(prompt);
349
+ if (fromPrompt)
350
+ return fromPrompt;
351
+ return detectFromCode(code);
352
+ }
353
+ /**
354
+ * Extract brand colors from live CSS file
355
+ */
356
+ function extractBrandColors() {
357
+ try {
358
+ const cssContent = fs.readFileSync(getAssetPath("css"), "utf-8");
359
+ const extractColor = (varName) => {
360
+ const match = cssContent.match(new RegExp(`${varName}:\\s*([#\\w]+)`));
361
+ return match ? match[1] : "";
362
+ };
363
+ return {
364
+ charcoal: extractColor("--sonance-charcoal") || "#343d46",
365
+ silver: extractColor("--sonance-silver") || "#e2e2e2",
366
+ white: extractColor("--sonance-white") || "#FFFFFF",
367
+ black: extractColor("--sonance-black") || "#000000",
368
+ teal: "#00D3C8", // Sonance accent
369
+ gray50: extractColor("--sonance-gray-50") || "#f8f9f9",
370
+ gray100: extractColor("--sonance-gray-100") || "#f0f1f2",
371
+ gray500: extractColor("--sonance-gray-500") || "#7a8389",
372
+ gray900: extractColor("--sonance-gray-900") || "#343d46",
373
+ };
374
+ }
375
+ catch {
376
+ // Return defaults if CSS can't be read
377
+ return {
378
+ charcoal: "#343d46",
379
+ silver: "#e2e2e2",
380
+ white: "#FFFFFF",
381
+ black: "#000000",
382
+ teal: "#00D3C8",
383
+ gray50: "#f8f9f9",
384
+ gray100: "#f0f1f2",
385
+ gray500: "#7a8389",
386
+ gray900: "#343d46",
387
+ };
388
+ }
389
+ }
390
+ /**
391
+ * Convert hex color to different formats for various document libraries
392
+ */
393
+ function hexToFormats(hex) {
394
+ const cleanHex = hex.replace("#", "");
395
+ const r = parseInt(cleanHex.substring(0, 2), 16);
396
+ const g = parseInt(cleanHex.substring(2, 4), 16);
397
+ const b = parseInt(cleanHex.substring(4, 6), 16);
398
+ return {
399
+ hex: `#${cleanHex}`,
400
+ hexNoHash: cleanHex.toUpperCase(),
401
+ rgb: { r, g, b },
402
+ rgbColor: `RGBColor(0x${cleanHex.substring(0, 2).toUpperCase()}, 0x${cleanHex.substring(2, 4).toUpperCase()}, 0x${cleanHex.substring(4, 6).toUpperCase()})`,
403
+ reportlab: `HexColor('#${cleanHex}')`,
404
+ };
405
+ }
406
+ /**
407
+ * Get brand colors in document-ready formats
408
+ */
409
+ function getBrandColorsForDocs() {
410
+ const colors = extractBrandColors();
411
+ return {
412
+ charcoal: hexToFormats(colors.charcoal),
413
+ silver: hexToFormats(colors.silver),
414
+ white: hexToFormats(colors.white),
415
+ black: hexToFormats(colors.black),
416
+ teal: hexToFormats(colors.teal),
417
+ gray50: hexToFormats(colors.gray50),
418
+ gray100: hexToFormats(colors.gray100),
419
+ gray500: hexToFormats(colors.gray500),
420
+ gray900: hexToFormats(colors.gray900),
421
+ };
422
+ }
423
+ const BRAND_PALETTES = {
424
+ sonance: {
425
+ primary: "#343d46", // Sonance Charcoal
426
+ secondary: "#e2e2e2", // Sonance Silver
427
+ accent: "#00D3C8", // Sonance Teal
428
+ background: "#FFFFFF",
429
+ foreground: "#343d46",
430
+ name: "Sonance",
431
+ preferredTheme: "light",
432
+ },
433
+ iport: {
434
+ primary: "#0F161D", // IPORT Dark
435
+ secondary: "#FC4C02", // IPORT Orange
436
+ accent: "#FC4C02", // IPORT Orange (accent)
437
+ background: "#0F161D",
438
+ foreground: "#FFFFFF",
439
+ name: "IPORT",
440
+ preferredTheme: "dark",
441
+ },
442
+ blaze: {
443
+ primary: "#28282B", // Blaze Dark
444
+ secondary: "#00A3E1", // Blaze Blue
445
+ accent: "#00A3E1", // Blaze Blue (primary accent)
446
+ accentSecondary: "#C02B0A", // Blaze Red (secondary accent)
447
+ background: "#28282B",
448
+ foreground: "#FFFFFF",
449
+ name: "Blaze Audio",
450
+ preferredTheme: "dark",
451
+ },
452
+ };
453
+ /**
454
+ * Get brand colors in document-ready formats for a specific brand
455
+ */
456
+ function getBrandColorsForDocsMulti(brand = "sonance") {
457
+ const palette = BRAND_PALETTES[brand];
458
+ return {
459
+ primary: hexToFormats(palette.primary),
460
+ secondary: hexToFormats(palette.secondary),
461
+ accent: hexToFormats(palette.accent),
462
+ accentSecondary: palette.accentSecondary ? hexToFormats(palette.accentSecondary) : hexToFormats(palette.accent),
463
+ background: hexToFormats(palette.background),
464
+ foreground: hexToFormats(palette.foreground),
465
+ white: hexToFormats("#FFFFFF"),
466
+ black: hexToFormats("#000000"),
467
+ };
468
+ }
469
+ /**
470
+ * Get regex patterns to detect brand colors in code
471
+ */
472
+ function getBrandColorPatterns(brand) {
473
+ const patterns = {
474
+ sonance: {
475
+ primaryPattern: /343d46|343D46|#343d46|0x34.*0x3D.*0x46|rgb\(52.*61.*70\)|charcoal|CHARCOAL|sonance-charcoal/i,
476
+ secondaryPattern: /e2e2e2|E2E2E2|#e2e2e2|0xE2.*0xE2.*0xE2|rgb\(226.*226.*226\)|silver|SILVER|sonance-silver/i,
477
+ accentPattern: /00D3C8|00d3c8|#00D3C8|0x00.*0xD3.*0xC8|rgb\(0.*211.*200\)|teal|TEAL/i,
478
+ },
479
+ iport: {
480
+ primaryPattern: /0F161D|0f161d|#0F161D|0x0F.*0x16.*0x1D|rgb\(15.*22.*29\)|iport.*dark/i,
481
+ secondaryPattern: /FC4C02|fc4c02|#FC4C02|0xFC.*0x4C.*0x02|rgb\(252.*76.*2\)|iport.*orange/i,
482
+ accentPattern: /FC4C02|fc4c02|#FC4C02|0xFC.*0x4C.*0x02|rgb\(252.*76.*2\)|iport.*orange/i,
483
+ },
484
+ blaze: {
485
+ primaryPattern: /28282B|28282b|#28282B|0x28.*0x28.*0x2B|rgb\(40.*40.*43\)|blaze.*dark/i,
486
+ secondaryPattern: /00A3E1|00a3e1|#00A3E1|0x00.*0xA3.*0xE1|rgb\(0.*163.*225\)|blaze.*blue/i,
487
+ accentPattern: /00A3E1|00a3e1|#00A3E1|C02B0A|c02b0a|#C02B0A|blaze.*(blue|red)/i,
488
+ },
489
+ };
490
+ return patterns[brand];
491
+ }
226
492
  /**
227
493
  * Sonance MCP Server
228
494
  *
@@ -384,7 +650,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
384
650
  },
385
651
  {
386
652
  name: "design_component",
387
- description: "Returns brand-specific design tokens, colors, implementation rules, and logo assets for designing a component. Smart defaults: Sonance/Light/Default logo lockup if not specified.",
653
+ description: "Returns brand-specific design tokens, colors, implementation rules, and logo assets for designing a component. Smart defaults: Sonance/Light/Default logo lockup if not specified. Auto-detects output type (ui/doc/marketing/tech) if not specified.",
388
654
  inputSchema: {
389
655
  type: "object",
390
656
  properties: {
@@ -403,14 +669,114 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
403
669
  enum: ["default", "sonance", "iport", "blaze"],
404
670
  description: "Which logo to use. 'default' = Sonance+James+IPORT lockup. Or specify 'sonance', 'iport', or 'blaze' for individual brand logos. Defaults to 'default' if not specified.",
405
671
  },
672
+ output_type: {
673
+ type: "string",
674
+ enum: ["ui", "doc", "marketing", "tech"],
675
+ description: "Type of output being designed. 'ui' = React/Next.js components, 'doc' = Word/PDF documents, 'marketing' = email/landing pages, 'tech' = documentation. Auto-detected if not specified.",
676
+ },
406
677
  component_description: {
407
678
  type: "string",
408
- description: "What the user wants to design (e.g., 'hero section', 'pricing card', 'navigation bar')",
679
+ description: "What the user wants to design (e.g., 'hero section', 'pricing card', 'navigation bar', 'proposal PDF', 'email template')",
409
680
  },
410
681
  },
411
682
  required: ["component_description"],
412
683
  },
413
684
  },
685
+ {
686
+ name: "evaluate_design",
687
+ description: "Evaluates a design or component against the Design Excellence Framework for Sonance, IPORT, or Blaze. Returns a score (0-100), tier (1-5), and specific feedback for improvement. Auto-detects output type and applies appropriate rubrics.",
688
+ inputSchema: {
689
+ type: "object",
690
+ properties: {
691
+ code: {
692
+ type: "string",
693
+ description: "The code/component to evaluate against brand guidelines",
694
+ },
695
+ description: {
696
+ type: "string",
697
+ description: "Brief description of what the component does",
698
+ },
699
+ brand: {
700
+ type: "string",
701
+ enum: ["sonance", "iport", "blaze"],
702
+ description: "Target brand for evaluation. Defaults to 'sonance' if not specified.",
703
+ },
704
+ output_type: {
705
+ type: "string",
706
+ enum: ["ui", "doc", "marketing", "tech"],
707
+ description: "Type of output being evaluated. 'ui' = React/Next.js, 'doc' = Word/PDF, 'marketing' = email/landing, 'tech' = documentation. Auto-detected from code if not specified.",
708
+ },
709
+ },
710
+ required: ["code"],
711
+ },
712
+ },
713
+ {
714
+ name: "get_document_template",
715
+ description: "Returns code templates for generating branded documents (Word, PDF, Excel) for Sonance, IPORT, or Blaze. Includes proper brand colors, fonts, and layout patterns for python-docx, ReportLab, openpyxl.",
716
+ inputSchema: {
717
+ type: "object",
718
+ properties: {
719
+ format: {
720
+ type: "string",
721
+ enum: ["word-python", "pdf-python", "excel-python"],
722
+ description: "The document format and language. word-python = python-docx, pdf-python = ReportLab, excel-python = openpyxl",
723
+ },
724
+ brand: {
725
+ type: "string",
726
+ enum: ["sonance", "iport", "blaze"],
727
+ description: "Target brand for the document. Defaults to 'sonance' if not specified.",
728
+ },
729
+ template_type: {
730
+ type: "string",
731
+ enum: ["proposal", "spec-sheet", "report", "invoice", "presentation"],
732
+ description: "Type of document being created",
733
+ },
734
+ },
735
+ required: ["format"],
736
+ },
737
+ },
738
+ {
739
+ name: "get_excellence_checklist",
740
+ description: "Returns the pre-delivery excellence checklist for Sonance UI work. Use this before delivering any component to ensure it meets Tier 4+ standards.",
741
+ inputSchema: {
742
+ type: "object",
743
+ properties: {},
744
+ required: [],
745
+ },
746
+ },
747
+ {
748
+ name: "get_anti_patterns",
749
+ description: "Returns common anti-patterns and mistakes to avoid when building Sonance-branded UI. Includes critical failures that result in Tier 1 scores.",
750
+ inputSchema: {
751
+ type: "object",
752
+ properties: {},
753
+ required: [],
754
+ },
755
+ },
756
+ {
757
+ name: "rebrand_document",
758
+ description: "Returns universal rebranding instructions for converting ANY existing document to a specific brand (Sonance, IPORT, or Blaze). Works with any document format - provides color replacements, font substitutions, and logo placement rules that apply regardless of document structure.",
759
+ inputSchema: {
760
+ type: "object",
761
+ properties: {
762
+ source_format: {
763
+ type: "string",
764
+ enum: ["word", "pdf", "excel", "powerpoint", "html", "generic"],
765
+ description: "What type of document you're rebranding. Use 'generic' if unsure or for other formats.",
766
+ },
767
+ brand: {
768
+ type: "string",
769
+ enum: ["sonance", "iport", "blaze"],
770
+ description: "Target brand for rebranding. Defaults to 'sonance' if not specified.",
771
+ },
772
+ current_colors: {
773
+ type: "string",
774
+ description: "Optional: describe or list colors currently in the document that need replacing (e.g., 'blue headers, gray backgrounds')",
775
+ },
776
+ },
777
+ required: ["source_format"],
778
+ },
779
+ },
414
780
  ],
415
781
  };
416
782
  });
@@ -646,6 +1012,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
646
1012
  const theme = rawArgs.theme || (() => { defaultsUsed.push("theme → Light"); return "light"; })();
647
1013
  const logoPreference = rawArgs.logo_preference || (() => { defaultsUsed.push("logo → Default lockup"); return "default"; })();
648
1014
  const component_description = rawArgs.component_description;
1015
+ // Smart output type detection
1016
+ const detectedOutputType = rawArgs.output_type || detectFromRequest(component_description) || "ui";
1017
+ if (!rawArgs.output_type) {
1018
+ defaultsUsed.push(`output type → ${detectedOutputType} (auto-detected)`);
1019
+ }
649
1020
  // Logo path mapping based on preference and theme
650
1021
  // Light theme = dark logo (for light backgrounds), Dark theme = light logo (for dark backgrounds)
651
1022
  const logoMap = {
@@ -843,31 +1214,176 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
843
1214
  }
844
1215
  // Build defaults notice if any were applied
845
1216
  const defaultsNotice = defaultsUsed.length > 0
846
- ? `\n> **Note**: Defaults applied: ${defaultsUsed.join(", ")}. Specify \`brand\`, \`theme\`, or \`logo_preference\` explicitly for different styling.\n`
1217
+ ? `\n> **Note**: Defaults applied: ${defaultsUsed.join(", ")}. Specify \`brand\`, \`theme\`, \`logo_preference\`, or \`output_type\` explicitly for different styling.\n`
847
1218
  : "";
848
- const response = `# Design Context for: ${component_description}
1219
+ // Get live brand colors for document formats
1220
+ const docColors = getBrandColorsForDocs();
1221
+ // Output-type-specific implementation guidance
1222
+ const outputTypeGuidance = {
1223
+ ui: `## Implementation: React/Next.js UI Component
849
1224
 
850
- **Brand**: ${brand.toUpperCase()}
851
- **Theme**: ${theme.charAt(0).toUpperCase() + theme.slice(1)}
852
- **Logo**: ${logoPreferenceLabel}
853
- ${defaultsNotice}
854
- ${tokens}
1225
+ ### Tailwind CSS Classes
1226
+ \`\`\`jsx
1227
+ // Primary Button
1228
+ <button className="bg-primary text-primary-foreground px-6 py-3 text-sm font-medium uppercase tracking-wide hover:bg-primary-hover transition-colors">
1229
+
1230
+ // Card
1231
+ <div className="bg-card border border-border rounded-sm p-6 shadow-sm">
855
1232
 
856
- ## Logo Asset
1233
+ // Section
1234
+ <section className="py-20 px-6 lg:py-24 lg:px-8 bg-background text-foreground">
1235
+ \`\`\`
1236
+
1237
+ ### Logo Asset
857
1238
  - **Selected**: ${logoPreferenceLabel}
858
1239
  - **Path**: \`${logoPath}\`
859
- - **Usage**:
1240
+ - **Usage**:
860
1241
  \`\`\`jsx
861
1242
  <img src="${logoPath}" alt="${logoPreferenceLabel}" className="h-8 w-auto" />
862
1243
  // or with Next.js Image:
863
1244
  <Image src="${logoPath}" alt="${logoPreferenceLabel}" width={200} height={40} />
864
1245
  \`\`\`
865
1246
 
866
- > **Tip**: To use a different logo, specify \`logo_preference\`: "default" (Sonance+James+IPORT), "sonance", "iport", or "blaze"
1247
+ ### Key Requirements
1248
+ - Use semantic Tailwind classes (bg-primary, not bg-[#343d46])
1249
+ - Support light/dark mode with \`dark:\` variants
1250
+ - Add hover states (hover:) and focus states (focus-visible:)
1251
+ - Use sharp corners (rounded-sm or rounded-none)
1252
+ - Generous spacing (py-20/py-24 for sections)`,
1253
+ doc: `## Implementation: Document Generation (Word/PDF/Excel)
1254
+
1255
+ ### Sonance Brand Constants for Documents
1256
+ \`\`\`python
1257
+ # python-docx / openpyxl colors
1258
+ CHARCOAL = '${docColors.charcoal.hexNoHash}' # For headers, text
1259
+ SILVER = '${docColors.silver.hexNoHash}' # For backgrounds, borders
1260
+ WHITE = '${docColors.white.hexNoHash}'
1261
+ BLACK = '${docColors.black.hexNoHash}'
1262
+ TEAL = '${docColors.teal.hexNoHash}' # Accent color
1263
+
1264
+ # As RGBColor (python-docx)
1265
+ from docx.shared import RGBColor
1266
+ CHARCOAL_RGB = ${docColors.charcoal.rgbColor}
1267
+ SILVER_RGB = ${docColors.silver.rgbColor}
1268
+ TEAL_RGB = ${docColors.teal.rgbColor}
1269
+ \`\`\`
1270
+
1271
+ ### Typography for Documents
1272
+ - **Font**: Montserrat (embed) or Arial (fallback)
1273
+ - **Title**: 28pt, Charcoal, Light weight
1274
+ - **Heading 1**: 18pt, Charcoal, Medium weight
1275
+ - **Heading 2**: 14pt, Charcoal, Medium weight
1276
+ - **Body**: 11pt, Charcoal, Regular weight
1277
+ - **Line spacing**: 1.15
1278
+
1279
+ ### Page Layout
1280
+ - **Margins**: 1.25" top, 1" sides, 1" bottom
1281
+ - **Header**: Logo + document title
1282
+ - **Footer**: Page numbers, confidentiality notice
1283
+
1284
+ ### Logo for Documents
1285
+ - **Path**: \`${logoPath}\`
1286
+ - **Size**: 2" wide in header
1287
+ - **Position**: Top-left or centered
1288
+
1289
+ > **Tip**: Use \`get_document_template\` tool for complete code templates.`,
1290
+ marketing: `## Implementation: Marketing Asset (Email/Landing Page)
1291
+
1292
+ ### Email-Safe CSS
1293
+ \`\`\`html
1294
+ <!-- Inline styles required for email clients -->
1295
+ <table style="background-color: ${docColors.charcoal.hex}; font-family: 'Montserrat', Arial, sans-serif;">
1296
+ <tr>
1297
+ <td style="color: ${docColors.white.hex}; font-size: 14px; line-height: 1.6;">
1298
+ Your content here
1299
+ </td>
1300
+ </tr>
1301
+ </table>
1302
+
1303
+ <!-- CTA Button (Outlook-safe) -->
1304
+ <table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse;">
1305
+ <tr>
1306
+ <td style="background-color: ${docColors.teal.hex}; border-radius: 2px; padding: 12px 24px;">
1307
+ <a href="#" style="color: ${docColors.white.hex}; text-decoration: none; font-weight: 500; text-transform: uppercase; letter-spacing: 0.08em;">
1308
+ Learn More
1309
+ </a>
1310
+ </td>
1311
+ </tr>
1312
+ </table>
1313
+ \`\`\`
1314
+
1315
+ ### Brand Colors (Inline)
1316
+ - Charcoal: \`${docColors.charcoal.hex}\`
1317
+ - Silver: \`${docColors.silver.hex}\`
1318
+ - Teal (CTA): \`${docColors.teal.hex}\`
1319
+ - White: \`${docColors.white.hex}\`
1320
+
1321
+ ### Logo for Email
1322
+ - **Path**: Use absolute URL to hosted logo
1323
+ - **Size**: 150-200px wide
1324
+ - **Alt text**: "Sonance" (required)
1325
+
1326
+ ### Key Requirements
1327
+ - All styles must be inline (no external CSS)
1328
+ - Use tables for layout (email compatibility)
1329
+ - Max width: 600px for email, 1200px for landing
1330
+ - Include fallback fonts: Arial, sans-serif`,
1331
+ tech: `## Implementation: Technical Documentation
1332
+
1333
+ ### Markdown Structure
1334
+ \`\`\`markdown
1335
+ # Document Title
1336
+
1337
+ Brief introduction paragraph.
1338
+
1339
+ ## Section Heading
1340
+
1341
+ Content with proper formatting.
1342
+
1343
+ ### Subsection
1344
+
1345
+ - Bullet points for lists
1346
+ - Keep items concise
1347
+
1348
+ \\\`\\\`\\\`typescript
1349
+ // Code blocks with syntax highlighting
1350
+ const example = "Sonance";
1351
+ \\\`\\\`\\\`
1352
+
1353
+ > **Note**: Use blockquotes for callouts
1354
+ \`\`\`
1355
+
1356
+ ### Documentation Style
1357
+ - **Headings**: Title case, no periods
1358
+ - **Code**: Syntax-highlighted blocks
1359
+ - **Lists**: Use bullets for unordered, numbers for steps
1360
+ - **Links**: Descriptive text, not "click here"
1361
+
1362
+ ### Brand Application
1363
+ - Use "Sonance" not "sonance" or "SONANCE" in prose
1364
+ - Maintain professional, premium tone
1365
+ - Generous spacing between sections
1366
+ - Clear hierarchy with heading levels
1367
+
1368
+ ### Logo for Documentation
1369
+ - Include logo in README header
1370
+ - Use dark variant for GitHub (light mode default)
1371
+ - Path: \`${logoPath}\``,
1372
+ };
1373
+ const response = `# Design Context for: ${component_description}
1374
+
1375
+ **Brand**: ${brand.toUpperCase()}
1376
+ **Theme**: ${theme.charAt(0).toUpperCase() + theme.slice(1)}
1377
+ **Output Type**: ${detectedOutputType.toUpperCase()}
1378
+ **Logo**: ${logoPreferenceLabel}
1379
+ ${defaultsNotice}
1380
+ ${tokens}
1381
+
1382
+ ${outputTypeGuidance[detectedOutputType]}
867
1383
 
868
1384
  ## Typography (All Brands)
869
1385
  - **Font Family**: Montserrat
870
- - **Headlines**: font-weight 300 (Light) or 500 (Medium), letter-spacing -0.02em
1386
+ - **Headlines**: font-weight 300 (Light) or 500 (Medium)
871
1387
  - **Body**: font-weight 400 (Regular), line-height 1.6
872
1388
  - **Buttons/CTAs**: font-weight 500, uppercase, letter-spacing 0.08em
873
1389
 
@@ -884,6 +1400,1150 @@ Now design the **${component_description}** following these tokens and principle
884
1400
  content: [{ type: "text", text: response }],
885
1401
  };
886
1402
  }
1403
+ case "evaluate_design": {
1404
+ const evalArgs = args;
1405
+ if (!evalArgs.code) {
1406
+ return {
1407
+ content: [{ type: "text", text: "Error: code is required for evaluation" }],
1408
+ isError: true,
1409
+ };
1410
+ }
1411
+ const code = evalArgs.code;
1412
+ const description = evalArgs.description || "Component";
1413
+ const evalBrand = evalArgs.brand || "sonance";
1414
+ const evalPalette = BRAND_PALETTES[evalBrand];
1415
+ // Smart output type detection
1416
+ const outputType = evalArgs.output_type || smartDetectOutputType(description, code);
1417
+ // Get brand-specific patterns for evaluation
1418
+ const brandPatterns = getBrandColorPatterns(evalBrand);
1419
+ // Output-type-specific checks
1420
+ let checks;
1421
+ let checkDescriptions;
1422
+ if (outputType === "doc") {
1423
+ // Document-specific checks (Word/PDF/Excel) - FORMAT-AGNOSTIC
1424
+ // These patterns work across python-docx, ReportLab, openpyxl, and manual edits
1425
+ // Brand-specific color patterns
1426
+ const primaryPattern = brandPatterns.primaryPattern;
1427
+ const secondaryPattern = brandPatterns.secondaryPattern;
1428
+ const accentPattern = brandPatterns.accentPattern;
1429
+ // Common non-brand colors that indicate incomplete rebranding (exclude target brand colors)
1430
+ const nonBrandColors = evalBrand === "sonance"
1431
+ ? /0066CC|0078D4|1E90FF|007BFF|2196F3|FF5722|FF9800|4CAF50|8BC34A|E91E63|9C27B0|673AB7|3F51B5|FC4C02|0F161D|28282B|00A3E1|C02B0A/i
1432
+ : evalBrand === "iport"
1433
+ ? /0066CC|0078D4|1E90FF|007BFF|2196F3|FF5722|FF9800|4CAF50|8BC34A|E91E63|9C27B0|673AB7|3F51B5|343d46|00D3C8|28282B|00A3E1|C02B0A/i
1434
+ : /0066CC|0078D4|1E90FF|007BFF|2196F3|FF5722|FF9800|4CAF50|8BC34A|E91E63|9C27B0|673AB7|3F51B5|343d46|00D3C8|FC4C02|0F161D/i;
1435
+ // Brand-specific logo patterns
1436
+ const logoPatterns = {
1437
+ sonance: /logo|sonance.*\.(png|jpg|svg)|Sonance_logo|add_picture|drawImage|Image\(/i,
1438
+ iport: /logo|iport.*\.(png|jpg|svg)|IPORT_Lockup|add_picture|drawImage|Image\(/i,
1439
+ blaze: /logo|blaze.*\.(png|jpg|svg)|Blaze_Lockup|add_picture|drawImage|Image\(/i,
1440
+ };
1441
+ checks = {
1442
+ // Brand Compliance (25%) - REBRANDING FOCUSED
1443
+ usesPrimaryColor: primaryPattern.test(code),
1444
+ usesSecondaryColor: secondaryPattern.test(code) || primaryPattern.test(code), // Secondary optional if primary present
1445
+ noOldBrandColors: !nonBrandColors.test(code),
1446
+ hasFontConfig: /Montserrat|Arial|font.*name|font_name|fontName|font-family/i.test(code),
1447
+ hasLogoReference: logoPatterns[evalBrand].test(code),
1448
+ // Typography (15%)
1449
+ hasFontSizes: /pt|Pt\(|font.*size|fontSize|font-size/i.test(code),
1450
+ hasHeadingStyles: /heading|title|Heading|style.*=.*'Heading|h1|h2|H1|H2/i.test(code),
1451
+ hasLineSpacing: /line.*spacing|lineSpacing|space_after|space_before|leading|line-height/i.test(code),
1452
+ // Layout (15%)
1453
+ hasMargins: /margin|Inches|Cm|Pt.*margin|padding/i.test(code),
1454
+ hasPageSetup: /page.*setup|section|orientation|paper.*size|pagesize/i.test(code),
1455
+ hasStructure: /add_paragraph|add_heading|add_table|Paragraph|Table|<table|<div|<section/i.test(code),
1456
+ // Visual Hierarchy (15%)
1457
+ hasMultipleStyles: /Heading 1|Heading 2|Title|Subtitle|style\[|h1.*h2|H1.*H2/i.test(code),
1458
+ usesProperWeights: !/font-weight.*700|fontWeight.*700|bold.*True|\.bold.*=.*True/i.test(code) || /font-weight.*(300|400|500)|fontWeight.*(300|400|500)|Medium|Light/i.test(code),
1459
+ // Medium Excellence (15%)
1460
+ hasErrorHandling: /try|except|catch|error|Error/i.test(code),
1461
+ hasComments: /^#|\/\/|\/\*|\"\"\"|<!--/m.test(code),
1462
+ isModular: /def |function |class |const.*=.*\(|export/i.test(code),
1463
+ // Polish (15%)
1464
+ hasHeaderFooter: /header|footer|page.*number|Header|Footer/i.test(code),
1465
+ hasWhitespace: /space_after|space_before|margin|padding|gap/i.test(code),
1466
+ };
1467
+ checkDescriptions = {
1468
+ usesPrimaryColor: { category: "Brand Compliance", description: `Uses ${evalPalette.name} primary color (${evalPalette.primary})`, weight: 8 },
1469
+ usesSecondaryColor: { category: "Brand Compliance", description: `Uses ${evalPalette.name} secondary color (${evalPalette.secondary}) or neutral colors`, weight: 4 },
1470
+ noOldBrandColors: { category: "Brand Compliance", description: `No old/non-${evalPalette.name} brand colors present`, weight: 6 },
1471
+ hasFontConfig: { category: "Brand Compliance", description: "Configures Montserrat or Arial font", weight: 5 },
1472
+ hasLogoReference: { category: "Brand Compliance", description: `References ${evalPalette.name} logo`, weight: 2 },
1473
+ hasFontSizes: { category: "Typography", description: "Defines font sizes", weight: 5 },
1474
+ hasHeadingStyles: { category: "Typography", description: "Uses heading styles", weight: 5 },
1475
+ hasLineSpacing: { category: "Typography", description: "Sets line spacing", weight: 5 },
1476
+ hasMargins: { category: "Layout", description: "Configures margins/padding", weight: 5 },
1477
+ hasPageSetup: { category: "Layout", description: "Has page setup", weight: 5 },
1478
+ hasStructure: { category: "Layout", description: "Uses structured document elements", weight: 5 },
1479
+ hasMultipleStyles: { category: "Visual Hierarchy", description: "Multiple heading levels", weight: 8 },
1480
+ usesProperWeights: { category: "Visual Hierarchy", description: "Uses proper font weights (not bold 700)", weight: 7 },
1481
+ hasErrorHandling: { category: "Medium Excellence", description: "Has error handling", weight: 5 },
1482
+ hasComments: { category: "Medium Excellence", description: "Has code comments", weight: 5 },
1483
+ isModular: { category: "Medium Excellence", description: "Uses modular code structure", weight: 5 },
1484
+ hasHeaderFooter: { category: "Polish", description: "Has header/footer", weight: 7 },
1485
+ hasWhitespace: { category: "Polish", description: "Has whitespace/spacing configuration", weight: 8 },
1486
+ };
1487
+ }
1488
+ else if (outputType === "marketing") {
1489
+ // Marketing-specific checks (Email/Landing)
1490
+ const primaryHex = evalPalette.primary.replace('#', '');
1491
+ checks = {
1492
+ // Brand Compliance (20%)
1493
+ usesInlineColors: new RegExp(`color.*:.*#?${primaryHex}|background.*:.*#?${primaryHex}`, 'i').test(code) || brandPatterns.primaryPattern.test(code),
1494
+ hasInlineStyles: /style\s*=/i.test(code),
1495
+ hasFallbackFonts: /Arial|sans-serif|font-family.*,/i.test(code),
1496
+ // Typography (15%)
1497
+ hasFontSizes: /font-size|fontSize/i.test(code),
1498
+ hasLineHeight: /line-height|lineHeight/i.test(code),
1499
+ hasTextFormatting: /text-transform|letter-spacing|font-weight/i.test(code),
1500
+ // Layout (15%)
1501
+ usesTableLayout: /<table|table.*border/i.test(code),
1502
+ hasMaxWidth: /max-width|maxWidth|width.*600|width.*100%/i.test(code),
1503
+ hasCellPadding: /cellpadding|padding/i.test(code),
1504
+ // Visual Hierarchy (15%)
1505
+ hasCTA: /button|btn|cta|call.*to.*action|href/i.test(code),
1506
+ hasHeadings: /<h[1-6]|font-size.*[2-4][0-9]px/i.test(code),
1507
+ // Medium Excellence (15%)
1508
+ isOutlookSafe: /mso-|<!--\[if|VML/i.test(code),
1509
+ hasAltText: /alt\s*=/i.test(code),
1510
+ hasResponsive: /media.*query|@media|max-width.*100%/i.test(code),
1511
+ // Polish (10%)
1512
+ hasPreheader: /preheader|preview.*text/i.test(code),
1513
+ hasUnsubscribe: /unsubscribe|opt.*out/i.test(code),
1514
+ };
1515
+ checkDescriptions = {
1516
+ usesInlineColors: { category: "Brand Compliance", description: `Uses ${evalPalette.name} colors inline`, weight: 7 },
1517
+ hasInlineStyles: { category: "Brand Compliance", description: "Uses inline styles (required)", weight: 7 },
1518
+ hasFallbackFonts: { category: "Brand Compliance", description: "Has font fallbacks", weight: 6 },
1519
+ hasFontSizes: { category: "Typography", description: "Defines font sizes", weight: 5 },
1520
+ hasLineHeight: { category: "Typography", description: "Sets line height", weight: 5 },
1521
+ hasTextFormatting: { category: "Typography", description: "Has text formatting", weight: 5 },
1522
+ usesTableLayout: { category: "Layout", description: "Uses table layout", weight: 5 },
1523
+ hasMaxWidth: { category: "Layout", description: "Has max-width constraint", weight: 5 },
1524
+ hasCellPadding: { category: "Layout", description: "Has proper spacing", weight: 5 },
1525
+ hasCTA: { category: "Visual Hierarchy", description: "Has call-to-action", weight: 8 },
1526
+ hasHeadings: { category: "Visual Hierarchy", description: "Has heading hierarchy", weight: 7 },
1527
+ isOutlookSafe: { category: "Medium Excellence", description: "Outlook compatible", weight: 5 },
1528
+ hasAltText: { category: "Medium Excellence", description: "Images have alt text", weight: 5 },
1529
+ hasResponsive: { category: "Medium Excellence", description: "Has responsive design", weight: 5 },
1530
+ hasPreheader: { category: "Polish", description: "Has preheader text", weight: 5 },
1531
+ hasUnsubscribe: { category: "Polish", description: "Has unsubscribe link", weight: 5 },
1532
+ };
1533
+ }
1534
+ else if (outputType === "tech") {
1535
+ // Technical documentation checks
1536
+ const brandNamePattern = new RegExp(evalPalette.name, 'i');
1537
+ checks = {
1538
+ // Brand Compliance (20%)
1539
+ usesBrandName: brandNamePattern.test(code),
1540
+ hasConsistentFormatting: /^#{1,3}\s/m.test(code),
1541
+ hasProfessionalTone: !/lol|haha|!!!|\?\?/i.test(code),
1542
+ // Typography (15%)
1543
+ hasHeadingHierarchy: /^#\s[\s\S]*^##\s/m.test(code),
1544
+ hasCodeBlocks: /```[\w]*\n/m.test(code),
1545
+ hasLists: /^[\-\*]\s|^\d+\.\s/m.test(code),
1546
+ // Layout (15%)
1547
+ hasTableOfContents: /table.*contents|\[.*\]\(#/i.test(code),
1548
+ hasSections: /^##\s/m.test(code),
1549
+ hasSpacing: /\n\n/.test(code),
1550
+ // Visual Hierarchy (15%)
1551
+ hasMultipleHeadingLevels: /^#\s[\s\S]*^##\s[\s\S]*^###\s/m.test(code),
1552
+ hasEmphasis: /\*\*.*\*\*|__.*__/m.test(code),
1553
+ // Medium Excellence (15%)
1554
+ hasLinks: /\[.*\]\(http/m.test(code),
1555
+ hasImageAlt: /!\[.+\]/m.test(code),
1556
+ hasCallouts: />\s\*\*|>\s\[!|:::/.test(code),
1557
+ // Polish (10%)
1558
+ hasIntro: /^#\s.*\n\n\w/m.test(code),
1559
+ hasConclusion: /conclusion|summary|next.*steps/i.test(code),
1560
+ };
1561
+ checkDescriptions = {
1562
+ usesBrandName: { category: "Brand Compliance", description: `Uses '${evalPalette.name}' correctly`, weight: 7 },
1563
+ hasConsistentFormatting: { category: "Brand Compliance", description: "Consistent markdown formatting", weight: 7 },
1564
+ hasProfessionalTone: { category: "Brand Compliance", description: "Professional tone", weight: 6 },
1565
+ hasHeadingHierarchy: { category: "Typography", description: "Proper heading hierarchy", weight: 5 },
1566
+ hasCodeBlocks: { category: "Typography", description: "Uses code blocks", weight: 5 },
1567
+ hasLists: { category: "Typography", description: "Uses lists appropriately", weight: 5 },
1568
+ hasTableOfContents: { category: "Layout", description: "Has navigation/TOC", weight: 5 },
1569
+ hasSections: { category: "Layout", description: "Has clear sections", weight: 5 },
1570
+ hasSpacing: { category: "Layout", description: "Has proper spacing", weight: 5 },
1571
+ hasMultipleHeadingLevels: { category: "Visual Hierarchy", description: "Multiple heading levels", weight: 8 },
1572
+ hasEmphasis: { category: "Visual Hierarchy", description: "Uses emphasis (bold)", weight: 7 },
1573
+ hasLinks: { category: "Medium Excellence", description: "Has external links", weight: 5 },
1574
+ hasImageAlt: { category: "Medium Excellence", description: "Images have alt text", weight: 5 },
1575
+ hasCallouts: { category: "Medium Excellence", description: "Has callouts/notes", weight: 5 },
1576
+ hasIntro: { category: "Polish", description: "Has introduction", weight: 5 },
1577
+ hasConclusion: { category: "Polish", description: "Has conclusion/next steps", weight: 5 },
1578
+ };
1579
+ }
1580
+ else {
1581
+ // UI Component checks (React/Next.js) - default
1582
+ checks = {
1583
+ // Brand Compliance (20%)
1584
+ usesSemantic: /bg-(primary|secondary|background|foreground|card|input)|text-(primary|secondary|foreground|muted)/.test(code),
1585
+ noHardcodedColors: !/#[0-9a-fA-F]{6}/.test(code) || /--sonance-|var\(--/.test(code),
1586
+ usesCorrectRadius: !/rounded-(xl|lg|2xl|3xl|full)/.test(code) || /rounded-(sm|none|md)/.test(code),
1587
+ // Typography (15%)
1588
+ usesMontserrat: /font-sans|Montserrat|--font-primary/.test(code),
1589
+ noFontBold: !/font-bold/.test(code) || /font-(light|medium|normal)/.test(code),
1590
+ hasTypographyHierarchy: /text-(xs|sm|base|lg|xl|2xl|3xl|4xl)/.test(code),
1591
+ // Color Application (10%)
1592
+ hasDarkMode: /dark:|data-theme|resolvedTheme/.test(code),
1593
+ usesVariables: /var\(--|--[a-z]/.test(code),
1594
+ // Layout & Spacing (15%)
1595
+ hasGenerousSpacing: /p[xy]?-(12|16|20|24|32)|space-(12|16|20|24)/.test(code),
1596
+ noTightSpacing: !/p[xy]?-[12](?!\d)/.test(code),
1597
+ // Visual Hierarchy (15%)
1598
+ hasMultipleSizes: (/text-sm/.test(code) && /text-(lg|xl|2xl)/.test(code)),
1599
+ hasWeightVariation: /font-(light|medium|normal)/.test(code),
1600
+ // Medium Excellence (15%)
1601
+ isTypeScript: /interface |type |: string|: number|: boolean|React\./.test(code),
1602
+ hasAccessibility: /aria-|role=|alt=/.test(code),
1603
+ hasTransitions: /transition|duration-/.test(code),
1604
+ // Polish (10%)
1605
+ hasHoverStates: /hover:/.test(code),
1606
+ hasFocusStates: /focus:|focus-visible:/.test(code),
1607
+ };
1608
+ checkDescriptions = {
1609
+ usesSemantic: { category: "Brand Compliance", description: "Uses semantic color classes", weight: 4 },
1610
+ noHardcodedColors: { category: "Brand Compliance", description: "No hardcoded hex colors", weight: 3 },
1611
+ usesCorrectRadius: { category: "Brand Compliance", description: "Uses sharp corners (rounded-sm)", weight: 3 },
1612
+ usesMontserrat: { category: "Typography", description: "Uses Montserrat font", weight: 4 },
1613
+ noFontBold: { category: "Typography", description: "No font-bold on headlines", weight: 4 },
1614
+ hasTypographyHierarchy: { category: "Typography", description: "Has typography hierarchy", weight: 2 },
1615
+ hasDarkMode: { category: "Color Application", description: "Supports dark mode", weight: 5 },
1616
+ usesVariables: { category: "Color Application", description: "Uses CSS variables", weight: 5 },
1617
+ hasGenerousSpacing: { category: "Layout", description: "Has generous spacing", weight: 5 },
1618
+ noTightSpacing: { category: "Layout", description: "No cramped layouts", weight: 5 },
1619
+ hasMultipleSizes: { category: "Visual Hierarchy", description: "Multiple font sizes", weight: 5 },
1620
+ hasWeightVariation: { category: "Visual Hierarchy", description: "Font weight variation", weight: 5 },
1621
+ isTypeScript: { category: "Medium Excellence", description: "Uses TypeScript", weight: 4 },
1622
+ hasAccessibility: { category: "Medium Excellence", description: "Has accessibility attrs", weight: 3 },
1623
+ hasTransitions: { category: "Medium Excellence", description: "Has transitions", weight: 3 },
1624
+ hasHoverStates: { category: "Polish", description: "Has hover states", weight: 5 },
1625
+ hasFocusStates: { category: "Polish", description: "Has focus states", weight: 5 },
1626
+ };
1627
+ }
1628
+ // Calculate score based on weights
1629
+ let totalScore = 0;
1630
+ const checkResults = [];
1631
+ for (const [key, passed] of Object.entries(checks)) {
1632
+ const desc = checkDescriptions[key];
1633
+ if (desc) {
1634
+ checkResults.push({
1635
+ check: key,
1636
+ passed,
1637
+ category: desc.category,
1638
+ description: desc.description,
1639
+ weight: desc.weight,
1640
+ });
1641
+ if (passed) {
1642
+ totalScore += desc.weight;
1643
+ }
1644
+ }
1645
+ }
1646
+ // Determine tier
1647
+ let tier;
1648
+ let tierLabel;
1649
+ let deliverable;
1650
+ if (totalScore >= 95) {
1651
+ tier = 5;
1652
+ tierLabel = "Portfolio-Worthy";
1653
+ deliverable = true;
1654
+ }
1655
+ else if (totalScore >= 85) {
1656
+ tier = 4;
1657
+ tierLabel = "Professional";
1658
+ deliverable = true;
1659
+ }
1660
+ else if (totalScore >= 70) {
1661
+ tier = 3;
1662
+ tierLabel = "Needs Iteration";
1663
+ deliverable = false;
1664
+ }
1665
+ else if (totalScore >= 50) {
1666
+ tier = 2;
1667
+ tierLabel = "Major Iteration Required";
1668
+ deliverable = false;
1669
+ }
1670
+ else {
1671
+ tier = 1;
1672
+ tierLabel = "Rebuild Required";
1673
+ deliverable = false;
1674
+ }
1675
+ // Build gaps list from failed checks
1676
+ const gaps = checkResults
1677
+ .filter(c => !c.passed)
1678
+ .map(c => c.description);
1679
+ // Group results by category
1680
+ const byCategory = {};
1681
+ for (const result of checkResults) {
1682
+ if (!byCategory[result.category]) {
1683
+ byCategory[result.category] = [];
1684
+ }
1685
+ byCategory[result.category].push(result);
1686
+ }
1687
+ // Build category scores table
1688
+ const categoryScores = Object.entries(byCategory).map(([category, results]) => {
1689
+ const earned = results.filter(r => r.passed).reduce((sum, r) => sum + r.weight, 0);
1690
+ const max = results.reduce((sum, r) => sum + r.weight, 0);
1691
+ const checkMarks = results.map(r => `${r.passed ? "✓" : "✗"} ${r.description}`).join(", ");
1692
+ return { category, earned, max, checkMarks };
1693
+ });
1694
+ // Output type labels
1695
+ const outputTypeLabels = {
1696
+ ui: "React/Next.js UI Component",
1697
+ doc: "Document (Word/PDF/Excel)",
1698
+ marketing: "Marketing Asset (Email/Landing)",
1699
+ tech: "Technical Documentation",
1700
+ };
1701
+ const evalResponse = `# Design Excellence Evaluation: ${description}
1702
+
1703
+ **Output Type**: ${outputTypeLabels[outputType]} (${outputType === evalArgs.output_type ? "specified" : "auto-detected"})
1704
+
1705
+ ## Score: ${totalScore}/100 — Tier ${tier} (${tierLabel})
1706
+
1707
+ ${deliverable ? "✅ **Ready for delivery**" : "⚠️ **Iterate before delivery** — minimum threshold is 85 (Tier 4)"}
1708
+
1709
+ ---
1710
+
1711
+ ## Dimension Scores
1712
+
1713
+ | Category | Score | Max | Details |
1714
+ |----------|-------|-----|---------|
1715
+ ${categoryScores.map(c => `| ${c.category} | ${c.earned} | ${c.max} | ${c.checkMarks} |`).join('\n')}
1716
+
1717
+ ---
1718
+
1719
+ ${gaps.length > 0 ? `## Gaps to Address
1720
+
1721
+ ${gaps.map((gap, i) => `${i + 1}. ${gap}`).join('\n')}
1722
+
1723
+ ---` : "## No major gaps identified — excellent work!"}
1724
+
1725
+ ## Next Steps
1726
+
1727
+ ${deliverable
1728
+ ? "This output meets Sonance brand standards. Consider polish improvements for Tier 5."
1729
+ : `Iterate on the gaps above to reach Tier 4 (85+). Current gap: ${85 - totalScore} points.`}
1730
+ `;
1731
+ return {
1732
+ content: [{ type: "text", text: evalResponse }],
1733
+ };
1734
+ }
1735
+ case "get_excellence_checklist": {
1736
+ const checklist = `# Sonance Pre-Delivery Excellence Checklist
1737
+
1738
+ Before delivering any Sonance-branded UI work, verify each item:
1739
+
1740
+ ## Typography
1741
+ - [ ] Montserrat font is loaded and applied
1742
+ - [ ] Headlines use weight 300 (light) or 500 (medium) — **never 700 (bold)**
1743
+ - [ ] Clear 4-level typography hierarchy
1744
+ - [ ] Appropriate line-height (1.5-1.75 for body)
1745
+
1746
+ ## Colors
1747
+ - [ ] All colors use semantic CSS variables (--background, --foreground, etc.)
1748
+ - [ ] No hardcoded hex colors in classes
1749
+ - [ ] Light mode works correctly
1750
+ - [ ] Dark mode works correctly
1751
+ - [ ] Contrast ratio ≥4.5:1 for text
1752
+
1753
+ ## Layout & Spacing
1754
+ - [ ] Generous section padding (80-120px vertical on desktop)
1755
+ - [ ] "Designed to Disappear" aesthetic — breathing room
1756
+ - [ ] Sharp corners (0-4px radius max)
1757
+ - [ ] No cramped layouts
1758
+
1759
+ ## Interactivity
1760
+ - [ ] Hover states on all clickable elements
1761
+ - [ ] Focus-visible states for keyboard navigation
1762
+ - [ ] Smooth transitions (200-400ms, ease-out)
1763
+ - [ ] No bouncy or dramatic animations
1764
+
1765
+ ## Accessibility
1766
+ - [ ] Semantic HTML elements
1767
+ - [ ] ARIA labels where needed
1768
+ - [ ] Alt text on images
1769
+ - [ ] Keyboard navigable
1770
+
1771
+ ## Code Quality
1772
+ - [ ] TypeScript types/interfaces
1773
+ - [ ] Responsive design (mobile-first)
1774
+ - [ ] No console errors
1775
+ - [ ] Clean, readable code
1776
+
1777
+ ---
1778
+
1779
+ ## Quality Gate
1780
+
1781
+ **Minimum for delivery:** Score 85+ on self-evaluation (Tier 4)
1782
+ **Target:** Score 95+ (Tier 5 — Portfolio-Worthy)
1783
+
1784
+ Use \`evaluate_design\` tool to score your work before delivery.
1785
+ `;
1786
+ return {
1787
+ content: [{ type: "text", text: checklist }],
1788
+ };
1789
+ }
1790
+ case "get_anti_patterns": {
1791
+ const antiPatterns = `# Sonance Anti-Patterns: What NOT to Do
1792
+
1793
+ ## Critical Failures (Immediate Tier 1)
1794
+
1795
+ These mistakes immediately drop your score to Tier 1 and require a rebuild:
1796
+
1797
+ ### 1. Bold Headlines
1798
+ \`\`\`jsx
1799
+ // ❌ WRONG — never use font-bold for headlines
1800
+ <h1 className="font-bold text-4xl">Welcome to Sonance</h1>
1801
+
1802
+ // ✅ CORRECT — use light (300) or medium (500)
1803
+ <h1 className="font-light text-4xl">Welcome to Sonance</h1>
1804
+ <h1 className="font-medium text-4xl">Welcome to Sonance</h1>
1805
+ \`\`\`
1806
+
1807
+ ### 2. Hardcoded Colors
1808
+ \`\`\`jsx
1809
+ // ❌ WRONG — hardcoded hex values
1810
+ <div className="bg-[#343d46] text-[#ffffff]">
1811
+
1812
+ // ✅ CORRECT — semantic tokens
1813
+ <div className="bg-primary text-primary-foreground">
1814
+ <div className="bg-sonance-charcoal text-white">
1815
+ \`\`\`
1816
+
1817
+ ### 3. Rounded Corners
1818
+ \`\`\`jsx
1819
+ // ❌ WRONG — too playful for Sonance
1820
+ <div className="rounded-xl">
1821
+ <div className="rounded-lg">
1822
+ <div className="rounded-2xl">
1823
+
1824
+ // ✅ CORRECT — sharp, premium aesthetic
1825
+ <div className="rounded-sm"> // 2px
1826
+ <div className="rounded-none"> // 0px
1827
+ <div className="rounded-md"> // 4px max
1828
+ \`\`\`
1829
+
1830
+ ### 4. Cramped Layouts
1831
+ \`\`\`jsx
1832
+ // ❌ WRONG — no breathing room
1833
+ <section className="py-4 px-2">
1834
+ <section className="py-8 px-4">
1835
+
1836
+ // ✅ CORRECT — "Designed to Disappear" generous spacing
1837
+ <section className="py-20 px-6 lg:py-24 lg:px-8">
1838
+ <section className="py-16 px-6 md:py-24">
1839
+ \`\`\`
1840
+
1841
+ ---
1842
+
1843
+ ## Common Mistakes (Tier 3 Traps)
1844
+
1845
+ These keep you stuck at Tier 3 (70-84), below the delivery threshold:
1846
+
1847
+ ### Wrong Font Family
1848
+ \`\`\`jsx
1849
+ // ❌ WRONG — generic fonts
1850
+ font-family: 'Inter', sans-serif;
1851
+ font-family: 'Roboto', sans-serif;
1852
+ className="font-inter"
1853
+
1854
+ // ✅ CORRECT — Montserrat only
1855
+ font-family: 'Montserrat', sans-serif;
1856
+ className="font-sans" // with proper Tailwind config
1857
+ \`\`\`
1858
+
1859
+ ### Light Mode Only
1860
+ \`\`\`jsx
1861
+ // ❌ WRONG — no dark mode support
1862
+ <div className="bg-white text-gray-900">
1863
+
1864
+ // ✅ CORRECT — theme-aware
1865
+ <div className="bg-background text-foreground">
1866
+ <div className="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
1867
+ \`\`\`
1868
+
1869
+ ### Missing Hover States
1870
+ \`\`\`jsx
1871
+ // ❌ WRONG — static button
1872
+ <button className="bg-primary text-white px-6 py-3">
1873
+
1874
+ // ✅ CORRECT — interactive feedback
1875
+ <button className="bg-primary text-white px-6 py-3 hover:bg-primary-hover transition-colors">
1876
+ \`\`\`
1877
+
1878
+ ### Missing Focus States
1879
+ \`\`\`jsx
1880
+ // ❌ WRONG — accessibility issue
1881
+ <button className="bg-primary">Click</button>
1882
+
1883
+ // ✅ CORRECT — keyboard accessible
1884
+ <button className="bg-primary focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary">
1885
+ \`\`\`
1886
+
1887
+ ### First-Draft Delivery
1888
+ \`\`\`
1889
+ // ❌ WRONG workflow
1890
+ Create → Deliver
1891
+
1892
+ // ✅ CORRECT workflow
1893
+ Create → Evaluate → Iterate → Evaluate → Deliver (when score ≥85)
1894
+ \`\`\`
1895
+
1896
+ ---
1897
+
1898
+ ## Quick Reference
1899
+
1900
+ | Anti-Pattern | Fix |
1901
+ |--------------|-----|
1902
+ | \`font-bold\` on headlines | \`font-light\` or \`font-medium\` |
1903
+ | \`#343d46\` hardcoded | \`bg-primary\` or \`var(--primary)\` |
1904
+ | \`rounded-lg\` | \`rounded-sm\` or \`rounded-none\` |
1905
+ | \`py-4\` sections | \`py-20\` or \`py-24\` |
1906
+ | Inter/Roboto font | Montserrat |
1907
+ | Light mode only | Add \`dark:\` variants |
1908
+ | No hover states | Add \`hover:\` classes |
1909
+ | No focus states | Add \`focus-visible:\` classes |
1910
+
1911
+ ---
1912
+
1913
+ **Remember:** The minimum delivery threshold is **85 points (Tier 4)**. Use \`evaluate_design\` to check your score.
1914
+ `;
1915
+ return {
1916
+ content: [{ type: "text", text: antiPatterns }],
1917
+ };
1918
+ }
1919
+ case "get_document_template": {
1920
+ const templateArgs = args;
1921
+ if (!templateArgs.format) {
1922
+ return {
1923
+ content: [{ type: "text", text: "Error: format is required (word-python, pdf-python, or excel-python)" }],
1924
+ isError: true,
1925
+ };
1926
+ }
1927
+ // Get brand-specific colors
1928
+ const targetBrand = templateArgs.brand || "sonance";
1929
+ const colors = getBrandColorsForDocsMulti(targetBrand);
1930
+ const palette = BRAND_PALETTES[targetBrand];
1931
+ const templateType = templateArgs.template_type || "report";
1932
+ // Brand-specific color names for comments
1933
+ const colorNames = {
1934
+ sonance: { primary: "CHARCOAL", secondary: "SILVER", accent: "TEAL" },
1935
+ iport: { primary: "IPORT_DARK", secondary: "IPORT_ORANGE", accent: "IPORT_ORANGE" },
1936
+ blaze: { primary: "BLAZE_DARK", secondary: "BLAZE_BLUE", accent: "BLAZE_BLUE" },
1937
+ };
1938
+ const brandNames = colorNames[targetBrand];
1939
+ const templates = {
1940
+ "word-python": `# ${palette.name} Word Document Template (python-docx)
1941
+
1942
+ \`\`\`python
1943
+ from docx import Document
1944
+ from docx.shared import Inches, Pt, RGBColor
1945
+ from docx.enum.text import WD_ALIGN_PARAGRAPH
1946
+ from docx.enum.style import WD_STYLE_TYPE
1947
+
1948
+ # ============================================
1949
+ # ${palette.name.toUpperCase()} BRAND CONSTANTS (Live from Component Library)
1950
+ # ============================================
1951
+ ${brandNames.primary} = RGBColor(0x${colors.primary.hexNoHash.slice(0, 2)}, 0x${colors.primary.hexNoHash.slice(2, 4)}, 0x${colors.primary.hexNoHash.slice(4, 6)})
1952
+ ${brandNames.secondary} = RGBColor(0x${colors.secondary.hexNoHash.slice(0, 2)}, 0x${colors.secondary.hexNoHash.slice(2, 4)}, 0x${colors.secondary.hexNoHash.slice(4, 6)})
1953
+ WHITE = RGBColor(0xFF, 0xFF, 0xFF)
1954
+ ${brandNames.accent} = RGBColor(0x${colors.accent.hexNoHash.slice(0, 2)}, 0x${colors.accent.hexNoHash.slice(2, 4)}, 0x${colors.accent.hexNoHash.slice(4, 6)})
1955
+
1956
+ FONT_PRIMARY = 'Montserrat'
1957
+ FONT_FALLBACK = 'Arial'
1958
+
1959
+ # ============================================
1960
+ # CREATE DOCUMENT
1961
+ # ============================================
1962
+ doc = Document()
1963
+
1964
+ # Set default font (will use fallback if Montserrat not installed)
1965
+ style = doc.styles['Normal']
1966
+ font = style.font
1967
+ font.name = FONT_FALLBACK # Use fallback; Montserrat if embedded
1968
+ font.size = Pt(11)
1969
+ font.color.rgb = ${brandNames.primary}
1970
+
1971
+ # Configure margins (1.25" top, 1" sides)
1972
+ sections = doc.sections
1973
+ for section in sections:
1974
+ section.top_margin = Inches(1.25)
1975
+ section.bottom_margin = Inches(1)
1976
+ section.left_margin = Inches(1)
1977
+ section.right_margin = Inches(1)
1978
+
1979
+ # ============================================
1980
+ # TITLE STYLE
1981
+ # ============================================
1982
+ title_style = doc.styles.add_style('${palette.name}Title', WD_STYLE_TYPE.PARAGRAPH)
1983
+ title_style.font.size = Pt(28)
1984
+ title_style.font.color.rgb = ${brandNames.primary}
1985
+ title_style.font.name = FONT_FALLBACK
1986
+ title_style.paragraph_format.space_after = Pt(24)
1987
+ title_style.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.LEFT
1988
+
1989
+ # ============================================
1990
+ # HEADING STYLES
1991
+ # ============================================
1992
+ h1_style = doc.styles.add_style('${palette.name}H1', WD_STYLE_TYPE.PARAGRAPH)
1993
+ h1_style.font.size = Pt(18)
1994
+ h1_style.font.color.rgb = ${brandNames.primary}
1995
+ h1_style.font.name = FONT_FALLBACK
1996
+ h1_style.paragraph_format.space_before = Pt(18)
1997
+ h1_style.paragraph_format.space_after = Pt(12)
1998
+
1999
+ h2_style = doc.styles.add_style('${palette.name}H2', WD_STYLE_TYPE.PARAGRAPH)
2000
+ h2_style.font.size = Pt(14)
2001
+ h2_style.font.color.rgb = ${brandNames.primary}
2002
+ h2_style.font.name = FONT_FALLBACK
2003
+ h2_style.paragraph_format.space_before = Pt(14)
2004
+ h2_style.paragraph_format.space_after = Pt(8)
2005
+
2006
+ # ============================================
2007
+ # ADD CONTENT
2008
+ # ============================================
2009
+ # Add logo (if available)
2010
+ # doc.add_picture('path/to/logo.png', width=Inches(2))
2011
+
2012
+ # Title
2013
+ doc.add_paragraph('${templateType === 'proposal' ? 'Project Proposal' : templateType === 'spec-sheet' ? 'Product Specification' : templateType === 'invoice' ? 'Invoice' : 'Report Title'}', style='${palette.name}Title')
2014
+
2015
+ # Introduction
2016
+ doc.add_paragraph('Section Heading', style='${palette.name}H1')
2017
+ doc.add_paragraph('Your content here. Use the ${palette.name} brand voice: professional, premium, pioneering.')
2018
+
2019
+ # Subsection
2020
+ doc.add_paragraph('Subsection', style='${palette.name}H2')
2021
+ doc.add_paragraph('Additional details with proper formatting.')
2022
+
2023
+ # ============================================
2024
+ # HEADER/FOOTER
2025
+ # ============================================
2026
+ header = doc.sections[0].header
2027
+ header_para = header.paragraphs[0]
2028
+ header_para.text = "${palette.name.toUpperCase()}"
2029
+ header_para.style.font.size = Pt(9)
2030
+ header_para.style.font.color.rgb = ${brandNames.primary}
2031
+
2032
+ footer = doc.sections[0].footer
2033
+ footer_para = footer.paragraphs[0]
2034
+ footer_para.text = "Confidential | Page "
2035
+ footer_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
2036
+
2037
+ # ============================================
2038
+ # SAVE
2039
+ # ============================================
2040
+ doc.save('${targetBrand}_${templateType}.docx')
2041
+ \`\`\`
2042
+
2043
+ ## Key Points
2044
+ - **Colors**: Use ${brandNames.primary} for text, ${brandNames.secondary} for backgrounds/borders
2045
+ - **Fonts**: Montserrat preferred, Arial as fallback
2046
+ - **Margins**: 1.25" top, 1" sides and bottom
2047
+ - **Hierarchy**: Title (28pt) > H1 (18pt) > H2 (14pt) > Body (11pt)
2048
+ ${palette.preferredTheme === "dark" ? `- **Note**: ${palette.name} prefers dark backgrounds - consider inverting` : ""}
2049
+ `,
2050
+ "pdf-python": `# ${palette.name} PDF Template (ReportLab)
2051
+
2052
+ \`\`\`python
2053
+ from reportlab.lib.pagesizes import letter
2054
+ from reportlab.lib.units import inch
2055
+ from reportlab.lib.colors import HexColor
2056
+ from reportlab.pdfgen import canvas
2057
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
2058
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image
2059
+
2060
+ # ============================================
2061
+ # ${palette.name.toUpperCase()} BRAND CONSTANTS (Live from Component Library)
2062
+ # ============================================
2063
+ ${brandNames.primary} = HexColor('${colors.primary.hex}')
2064
+ ${brandNames.secondary} = HexColor('${colors.secondary.hex}')
2065
+ WHITE = HexColor('${colors.white.hex}')
2066
+ ${brandNames.accent} = HexColor('${colors.accent.hex}')
2067
+
2068
+ FONT_PRIMARY = 'Helvetica' # Use Helvetica (built-in), or register Montserrat
2069
+
2070
+ # ============================================
2071
+ # STYLES
2072
+ # ============================================
2073
+ styles = getSampleStyleSheet()
2074
+
2075
+ title_style = ParagraphStyle(
2076
+ '${palette.name}Title',
2077
+ parent=styles['Heading1'],
2078
+ fontName=FONT_PRIMARY,
2079
+ fontSize=28,
2080
+ textColor=${brandNames.primary},
2081
+ spaceAfter=24,
2082
+ )
2083
+
2084
+ h1_style = ParagraphStyle(
2085
+ '${palette.name}H1',
2086
+ parent=styles['Heading2'],
2087
+ fontName=FONT_PRIMARY,
2088
+ fontSize=18,
2089
+ textColor=${brandNames.primary},
2090
+ spaceBefore=18,
2091
+ spaceAfter=12,
2092
+ )
2093
+
2094
+ h2_style = ParagraphStyle(
2095
+ '${palette.name}H2',
2096
+ parent=styles['Heading3'],
2097
+ fontName=FONT_PRIMARY,
2098
+ fontSize=14,
2099
+ textColor=${brandNames.primary},
2100
+ spaceBefore=14,
2101
+ spaceAfter=8,
2102
+ )
2103
+
2104
+ body_style = ParagraphStyle(
2105
+ '${palette.name}Body',
2106
+ parent=styles['Normal'],
2107
+ fontName=FONT_PRIMARY,
2108
+ fontSize=11,
2109
+ textColor=${brandNames.primary},
2110
+ leading=15, # Line height
2111
+ )
2112
+
2113
+ # ============================================
2114
+ # CREATE PDF
2115
+ # ============================================
2116
+ doc = SimpleDocTemplate(
2117
+ '${targetBrand}_${templateType}.pdf',
2118
+ pagesize=letter,
2119
+ topMargin=1.25*inch,
2120
+ bottomMargin=1*inch,
2121
+ leftMargin=1*inch,
2122
+ rightMargin=1*inch,
2123
+ )
2124
+
2125
+ # Content
2126
+ content = []
2127
+
2128
+ # Logo (optional)
2129
+ # content.append(Image('logo.png', width=2*inch, height=0.5*inch))
2130
+
2131
+ # Title
2132
+ content.append(Paragraph('${templateType === 'proposal' ? 'Project Proposal' : templateType === 'spec-sheet' ? 'Product Specification' : templateType === 'invoice' ? 'Invoice' : 'Report Title'}', title_style))
2133
+
2134
+ # Section
2135
+ content.append(Paragraph('Section Heading', h1_style))
2136
+ content.append(Paragraph('Your content here. Use the ${palette.name} brand voice: professional, premium, pioneering.', body_style))
2137
+
2138
+ # Subsection
2139
+ content.append(Spacer(1, 12))
2140
+ content.append(Paragraph('Subsection', h2_style))
2141
+ content.append(Paragraph('Additional details with proper formatting.', body_style))
2142
+
2143
+ # Build PDF
2144
+ doc.build(content)
2145
+ \`\`\`
2146
+
2147
+ ## Key Points
2148
+ - **Colors**: Use HexColor('${colors.primary.hex}') for text
2149
+ - **Fonts**: Helvetica (built-in) or register Montserrat TTF
2150
+ - **Margins**: 1.25" top, 1" sides and bottom
2151
+ - **Leading**: 15pt for body text (1.4x line height)
2152
+ ${palette.preferredTheme === "dark" ? `- **Note**: ${palette.name} prefers dark backgrounds - consider inverting` : ""}
2153
+ `,
2154
+ "excel-python": `# ${palette.name} Excel Template (openpyxl)
2155
+
2156
+ \`\`\`python
2157
+ from openpyxl import Workbook
2158
+ from openpyxl.styles import Font, Fill, PatternFill, Alignment, Border, Side
2159
+ from openpyxl.utils import get_column_letter
2160
+
2161
+ # ============================================
2162
+ # ${palette.name.toUpperCase()} BRAND CONSTANTS (Live from Component Library)
2163
+ # ============================================
2164
+ ${brandNames.primary} = '${colors.primary.hexNoHash}'
2165
+ ${brandNames.secondary} = '${colors.secondary.hexNoHash}'
2166
+ WHITE = '${colors.white.hexNoHash}'
2167
+ ${brandNames.accent} = '${colors.accent.hexNoHash}'
2168
+
2169
+ FONT_PRIMARY = 'Arial' # Excel-safe fallback for Montserrat
2170
+
2171
+ # ============================================
2172
+ # STYLES
2173
+ # ============================================
2174
+ # Header style
2175
+ header_font = Font(name=FONT_PRIMARY, size=12, bold=True, color=WHITE)
2176
+ header_fill = PatternFill(start_color=${brandNames.primary}, end_color=${brandNames.primary}, fill_type='solid')
2177
+ header_alignment = Alignment(horizontal='center', vertical='center')
2178
+
2179
+ # Body style
2180
+ body_font = Font(name=FONT_PRIMARY, size=11, color=${brandNames.primary})
2181
+ body_alignment = Alignment(horizontal='left', vertical='center')
2182
+
2183
+ # Accent style (for totals, highlights)
2184
+ accent_font = Font(name=FONT_PRIMARY, size=11, bold=True, color=WHITE)
2185
+ accent_fill = PatternFill(start_color=${brandNames.accent}, end_color=${brandNames.accent}, fill_type='solid')
2186
+
2187
+ # Border style
2188
+ thin_border = Border(
2189
+ left=Side(style='thin', color=${brandNames.secondary}),
2190
+ right=Side(style='thin', color=${brandNames.secondary}),
2191
+ top=Side(style='thin', color=${brandNames.secondary}),
2192
+ bottom=Side(style='thin', color=${brandNames.secondary}),
2193
+ )
2194
+
2195
+ # ============================================
2196
+ # CREATE WORKBOOK
2197
+ # ============================================
2198
+ wb = Workbook()
2199
+ ws = wb.active
2200
+ ws.title = '${templateType === 'spec-sheet' ? 'Specifications' : templateType === 'invoice' ? 'Invoice' : 'Data'}'
2201
+
2202
+ # ============================================
2203
+ # HEADER ROW
2204
+ # ============================================
2205
+ headers = ['Item', 'Description', 'Quantity', 'Price', 'Total']
2206
+ for col, header in enumerate(headers, 1):
2207
+ cell = ws.cell(row=1, column=col, value=header)
2208
+ cell.font = header_font
2209
+ cell.fill = header_fill
2210
+ cell.alignment = header_alignment
2211
+ cell.border = thin_border
2212
+
2213
+ # ============================================
2214
+ # DATA ROWS
2215
+ # ============================================
2216
+ data = [
2217
+ ['Product A', 'High-quality audio solution', 2, 1500.00, 3000.00],
2218
+ ['Product B', 'Premium installation kit', 1, 250.00, 250.00],
2219
+ ]
2220
+
2221
+ for row_num, row_data in enumerate(data, 2):
2222
+ for col_num, value in enumerate(row_data, 1):
2223
+ cell = ws.cell(row=row_num, column=col_num, value=value)
2224
+ cell.font = body_font
2225
+ cell.alignment = body_alignment
2226
+ cell.border = thin_border
2227
+
2228
+ # Format currency columns
2229
+ if col_num in [4, 5]:
2230
+ cell.number_format = '$#,##0.00'
2231
+
2232
+ # ============================================
2233
+ # TOTAL ROW
2234
+ # ============================================
2235
+ total_row = len(data) + 2
2236
+ ws.cell(row=total_row, column=4, value='Total:').font = accent_font
2237
+ total_cell = ws.cell(row=total_row, column=5, value='=SUM(E2:E{})'.format(total_row - 1))
2238
+ total_cell.font = accent_font
2239
+ total_cell.fill = accent_fill
2240
+ total_cell.number_format = '$#,##0.00'
2241
+
2242
+ # ============================================
2243
+ # COLUMN WIDTHS
2244
+ # ============================================
2245
+ column_widths = [15, 35, 12, 12, 15]
2246
+ for i, width in enumerate(column_widths, 1):
2247
+ ws.column_dimensions[get_column_letter(i)].width = width
2248
+
2249
+ # ============================================
2250
+ # SAVE
2251
+ # ============================================
2252
+ wb.save('${targetBrand}_${templateType}.xlsx')
2253
+ \`\`\`
2254
+
2255
+ ## Key Points
2256
+ - **Colors**: Use '${colors.primary.hexNoHash}' (no #) for openpyxl
2257
+ - **Header**: ${brandNames.primary} background, white text
2258
+ - **Accent**: ${brandNames.accent} for totals and highlights
2259
+ - **Border**: ${brandNames.secondary} thin borders
2260
+ - **Font**: Arial (Excel-safe Montserrat alternative)
2261
+ ${palette.preferredTheme === "dark" ? `- **Note**: ${palette.name} prefers dark backgrounds` : ""}
2262
+ `,
2263
+ };
2264
+ const template = templates[templateArgs.format];
2265
+ if (!template) {
2266
+ return {
2267
+ content: [{ type: "text", text: `Unknown format: ${templateArgs.format}` }],
2268
+ isError: true,
2269
+ };
2270
+ }
2271
+ return {
2272
+ content: [{ type: "text", text: template }],
2273
+ };
2274
+ }
2275
+ case "rebrand_document": {
2276
+ const rebrandArgs = args;
2277
+ if (!rebrandArgs.source_format) {
2278
+ return {
2279
+ content: [{ type: "text", text: "Error: source_format is required (word, pdf, excel, powerpoint, html, or generic)" }],
2280
+ isError: true,
2281
+ };
2282
+ }
2283
+ // Get brand-specific colors
2284
+ const targetBrand = rebrandArgs.brand || "sonance";
2285
+ const brandColors = getBrandColorsForDocsMulti(targetBrand);
2286
+ const palette = BRAND_PALETTES[targetBrand];
2287
+ // Build format-specific color syntax dynamically based on brand
2288
+ const buildColorSyntax = (brand) => {
2289
+ const c = getBrandColorsForDocsMulti(brand);
2290
+ return {
2291
+ word: {
2292
+ primary: `${c.primary.rgbColor} or '${c.primary.hexNoHash}'`,
2293
+ secondary: `${c.secondary.rgbColor} or '${c.secondary.hexNoHash}'`,
2294
+ accent: `${c.accent.rgbColor} or '${c.accent.hexNoHash}'`,
2295
+ white: `RGBColor(0xFF, 0xFF, 0xFF) or 'FFFFFF'`,
2296
+ },
2297
+ pdf: {
2298
+ primary: `HexColor('${c.primary.hex}')`,
2299
+ secondary: `HexColor('${c.secondary.hex}')`,
2300
+ accent: `HexColor('${c.accent.hex}')`,
2301
+ white: `HexColor('#FFFFFF')`,
2302
+ },
2303
+ excel: {
2304
+ primary: `'${c.primary.hexNoHash}' (no # prefix for openpyxl)`,
2305
+ secondary: `'${c.secondary.hexNoHash}'`,
2306
+ accent: `'${c.accent.hexNoHash}'`,
2307
+ white: `'FFFFFF'`,
2308
+ },
2309
+ powerpoint: {
2310
+ primary: `RGBColor(${c.primary.rgb.r}, ${c.primary.rgb.g}, ${c.primary.rgb.b}) or ${c.primary.hex}`,
2311
+ secondary: `RGBColor(${c.secondary.rgb.r}, ${c.secondary.rgb.g}, ${c.secondary.rgb.b}) or ${c.secondary.hex}`,
2312
+ accent: `RGBColor(${c.accent.rgb.r}, ${c.accent.rgb.g}, ${c.accent.rgb.b}) or ${c.accent.hex}`,
2313
+ white: `RGBColor(255, 255, 255) or #FFFFFF`,
2314
+ },
2315
+ html: {
2316
+ primary: `${c.primary.hex} or rgb(${c.primary.rgb.r}, ${c.primary.rgb.g}, ${c.primary.rgb.b})`,
2317
+ secondary: `${c.secondary.hex} or rgb(${c.secondary.rgb.r}, ${c.secondary.rgb.g}, ${c.secondary.rgb.b})`,
2318
+ accent: `${c.accent.hex} or rgb(${c.accent.rgb.r}, ${c.accent.rgb.g}, ${c.accent.rgb.b})`,
2319
+ white: `#FFFFFF or rgb(255, 255, 255)`,
2320
+ },
2321
+ generic: {
2322
+ primary: `${c.primary.hex} (${palette.name} Primary)`,
2323
+ secondary: `${c.secondary.hex} (${palette.name} Secondary)`,
2324
+ accent: `${c.accent.hex} (${palette.name} Accent)`,
2325
+ white: `#FFFFFF (White)`,
2326
+ },
2327
+ };
2328
+ };
2329
+ const colorSyntax = buildColorSyntax(targetBrand);
2330
+ const syntax = colorSyntax[rebrandArgs.source_format] || colorSyntax.generic;
2331
+ // Brand-specific color names
2332
+ const colorNames = {
2333
+ sonance: { primary: "Charcoal", secondary: "Silver", accent: "Teal" },
2334
+ iport: { primary: "IPORT Dark", secondary: "Orange", accent: "Orange" },
2335
+ blaze: { primary: "Blaze Dark", secondary: "Blue", accent: "Blue/Red" },
2336
+ };
2337
+ const brandColorNames = colorNames[targetBrand];
2338
+ // Brand-specific logo guidance
2339
+ const logoGuidance = {
2340
+ sonance: `- **Light backgrounds**: Use \`Sonance_logo_dark_mode.png\`
2341
+ - **Dark backgrounds**: Use \`Sonance_logo_light_mode.png\`
2342
+ - **Path**: \`assets/logos/Sonance_logo_[light/dark]_mode.png\``,
2343
+ iport: `- **Light backgrounds**: Use \`IPORT_Lockup_2C_Dark.png\`
2344
+ - **Dark backgrounds**: Use \`IPORT_Lockup_2C_Light.png\`
2345
+ - **Note**: IPORT prefers dark backgrounds
2346
+ - **Path**: \`assets/logos/IPORT_Lockup_*.png\``,
2347
+ blaze: `- **Light backgrounds**: Use \`Blaze_Lockup_2C_Light.png\`
2348
+ - **Dark backgrounds**: Use \`Blaze_Lockup_3C_Dark.png\`
2349
+ - **Note**: Blaze prefers dark backgrounds
2350
+ - **Path**: \`assets/logos/Blaze_Lockup_*.png\``,
2351
+ };
2352
+ // Build custom color replacement guidance if user provided current colors
2353
+ let customColorSection = "";
2354
+ if (rebrandArgs.current_colors) {
2355
+ customColorSection = `
2356
+ ### Your Specific Replacements
2357
+
2358
+ Based on your description ("${rebrandArgs.current_colors}"):
2359
+
2360
+ | What You Have | Replace With | ${palette.name} Color |
2361
+ |---------------|--------------|---------------|
2362
+ | Blue elements | ${syntax.primary} | ${brandColorNames.primary} (primary) |
2363
+ | Gray backgrounds | ${syntax.secondary} | ${brandColorNames.secondary} (secondary) |
2364
+ | Accent/highlight colors | ${syntax.accent} | ${brandColorNames.accent} (accent) |
2365
+ | Black text | ${syntax.primary} | ${brandColorNames.primary} (softer than pure black) |
2366
+ | White backgrounds | Keep as-is or use ${palette.preferredTheme === "dark" ? syntax.primary : "white"} | ${palette.preferredTheme === "dark" ? "Consider dark background" : "White is approved"} |
2367
+
2368
+ `;
2369
+ }
2370
+ const rebrandGuide = `# Universal Rebranding Guide: ${rebrandArgs.source_format.toUpperCase()} → ${palette.name}
2371
+
2372
+ This guide helps you rebrand **any ${rebrandArgs.source_format} document** to ${palette.name} standards, regardless of its current design or structure.
2373
+
2374
+ ${palette.preferredTheme === "dark" ? `> **Note:** ${palette.name} prefers **dark backgrounds**. Consider inverting light documents.` : ""}
2375
+
2376
+ ---
2377
+
2378
+ ## Core Principle
2379
+
2380
+ **Don't redesign the layout—just replace the brand elements:**
2381
+ 1. Colors → ${palette.name} palette
2382
+ 2. Fonts → Montserrat (or Arial fallback)
2383
+ 3. Logos → ${palette.name} logo
2384
+
2385
+ The document's structure stays the same; only the brand identity changes.
2386
+
2387
+ ---
2388
+
2389
+ ## Color Replacements
2390
+
2391
+ ### ${palette.name} Brand Palette
2392
+
2393
+ | Color | Hex | ${rebrandArgs.source_format.toUpperCase()} Format | Usage |
2394
+ |-------|-----|--------|-------|
2395
+ | **${brandColorNames.primary}** | ${brandColors.primary.hex} | ${syntax.primary} | Headers, body text, primary elements |
2396
+ | **${brandColorNames.secondary}** | ${brandColors.secondary.hex} | ${syntax.secondary} | ${palette.preferredTheme === "dark" ? "Accents, CTAs, highlights" : "Backgrounds, borders, dividers"} |
2397
+ | **${brandColorNames.accent}** | ${brandColors.accent.hex} | ${syntax.accent} | Accents, CTAs, highlights |
2398
+ | **White** | #FFFFFF | ${syntax.white} | ${palette.preferredTheme === "dark" ? "Text on dark backgrounds" : "Backgrounds, text on dark"} |
2399
+ | **Black** | #000000 | Use sparingly | Only for maximum contrast |
2400
+
2401
+ ### Universal Replacement Rules
2402
+
2403
+ | Find (Any Of These) | Replace With |
2404
+ |---------------------|--------------|
2405
+ | Blues (#0066CC, #0078D4, #1E90FF, navy, etc.) | ${brandColorNames.primary} ${brandColors.primary.hex} |
2406
+ | Reds (#FF0000, #CC0000, crimson, etc.) | ${brandColorNames.primary} ${brandColors.primary.hex} or ${brandColorNames.accent} ${brandColors.accent.hex} for CTAs |
2407
+ | Greens (#00FF00, #228B22, etc.) | ${brandColorNames.accent} ${brandColors.accent.hex} |
2408
+ | Oranges/Yellows | ${brandColorNames.accent} ${brandColors.accent.hex} |
2409
+ | Light grays (#F0F0F0, #EEEEEE, #D3D3D3) | ${brandColorNames.secondary} ${brandColors.secondary.hex} |
2410
+ | Dark grays (#333333, #666666, #555555) | ${brandColorNames.primary} ${brandColors.primary.hex} |
2411
+ | Pure black text (#000000) | ${brandColorNames.primary} ${brandColors.primary.hex} (softer, more premium) |
2412
+ ${customColorSection}
2413
+ ---
2414
+
2415
+ ## Typography Replacements
2416
+
2417
+ ### Font Substitution Map
2418
+
2419
+ | Current Font | Replace With | Notes |
2420
+ |--------------|--------------|-------|
2421
+ | Arial | Montserrat (or keep Arial as fallback) | Sans-serif ✓ |
2422
+ | Helvetica | Montserrat | Very similar feel |
2423
+ | Calibri | Montserrat | Microsoft default → ${palette.name} |
2424
+ | Times New Roman | Montserrat | Serif → Sans-serif |
2425
+ | Georgia | Montserrat | Serif → Sans-serif |
2426
+ | Verdana | Montserrat | |
2427
+ | Any other font | Montserrat | Default to brand font |
2428
+
2429
+ ### Weight Mapping
2430
+
2431
+ | Current Weight | ${palette.name} Weight | Usage |
2432
+ |----------------|----------------|-------|
2433
+ | Bold (700) for headers | **Medium (500)** or **Light (300)** | Headlines should NOT be bold |
2434
+ | Bold (700) for emphasis | Medium (500) | In-line emphasis |
2435
+ | Regular (400) | Regular (400) | Body text |
2436
+ | Light (300) | Light (300) | Large display text |
2437
+
2438
+ ### Size Guidelines (Documents)
2439
+
2440
+ | Element | Size |
2441
+ |---------|------|
2442
+ | Document Title | 28pt |
2443
+ | Heading 1 | 18pt |
2444
+ | Heading 2 | 14pt |
2445
+ | Body Text | 11pt |
2446
+ | Captions/Footnotes | 9pt |
2447
+
2448
+ ---
2449
+
2450
+ ## Logo Replacement
2451
+
2452
+ ### Logo Assets
2453
+ ${logoGuidance[targetBrand]}
2454
+
2455
+ ### Placement Rules
2456
+
2457
+ | Placement | Size | Position |
2458
+ |-----------|------|----------|
2459
+ | Document Header | 2" wide (150-200px) | Top-left or centered |
2460
+ | Cover Page | 3" wide (250-300px) | Centered, upper third |
2461
+ | Footer | 1" wide (75-100px) | Bottom-left or centered |
2462
+ | Email Signature | 150px wide | Left-aligned |
2463
+
2464
+ ### Clear Space
2465
+ Maintain clear space around the logo equal to the height of the first letter on all sides.
2466
+
2467
+ ---
2468
+
2469
+ ## Whitespace & Spacing
2470
+
2471
+ ### The "Designed to Disappear" Principle
2472
+ ${palette.name} aesthetic requires **generous whitespace**. When rebranding:
2473
+
2474
+ - **Don't compress** — if the original has tight spacing, expand it
2475
+ - **Section padding**: Minimum 40-60pt between major sections
2476
+ - **Paragraph spacing**: 1.15 line height, 6-12pt after paragraphs
2477
+ - **Margins**: 1.25" top, 1" sides (for print documents)
2478
+
2479
+ ---
2480
+
2481
+ ## Quick Checklist
2482
+
2483
+ Before considering your rebrand complete:
2484
+
2485
+ - [ ] All brand colors replaced with ${brandColorNames.primary}/${brandColorNames.secondary}/${brandColorNames.accent} palette
2486
+ - [ ] No original brand colors remain
2487
+ - [ ] Font changed to Montserrat (or Arial fallback)
2488
+ - [ ] Headlines use weight 300-500 (not bold 700)
2489
+ - [ ] Logo replaced with ${palette.name} logo
2490
+ - [ ] Logo has proper clear space
2491
+ - [ ] Adequate whitespace maintained
2492
+ - [ ] Document feels premium and "breathable"
2493
+ ${palette.preferredTheme === "dark" ? `- [ ] Dark background applied (${palette.name} preference)` : ""}
2494
+
2495
+ ---
2496
+
2497
+ ## Format-Specific Tips: ${rebrandArgs.source_format.toUpperCase()}
2498
+
2499
+ ${rebrandArgs.source_format === "word" ? `
2500
+ ### Word/DOCX
2501
+ - Use **Find & Replace** (Ctrl+H) for text colors
2502
+ - Update **Styles** (Heading 1, Heading 2, Normal) for global changes
2503
+ - Set default font in **Design > Fonts**
2504
+ - Header/Footer: Insert > Header > Edit, add logo
2505
+ ` : ""}${rebrandArgs.source_format === "pdf" ? `
2506
+ ### PDF
2507
+ - Use **Adobe Acrobat Pro** Edit PDF feature, or
2508
+ - Regenerate from source (Word, InDesign, etc.)
2509
+ - For ReportLab/python: Use \`HexColor('${brandColors.primary.hex}')\`
2510
+ - Embed Montserrat font or use Helvetica (built-in)
2511
+ ` : ""}${rebrandArgs.source_format === "excel" ? `
2512
+ ### Excel
2513
+ - Use **Cell Styles** for consistent formatting
2514
+ - Header row: Fill color \`${brandColors.primary.hexNoHash}\`, font color \`FFFFFF\`
2515
+ - Data rows: Font color \`${brandColors.primary.hexNoHash}\`
2516
+ - Borders: Color \`${brandColors.secondary.hexNoHash}\`
2517
+ - Accent cells: Fill \`${brandColors.accent.hexNoHash}\`
2518
+ ` : ""}${rebrandArgs.source_format === "powerpoint" ? `
2519
+ ### PowerPoint
2520
+ - Update **Slide Master** for global changes
2521
+ - Theme colors: Edit in Design > Variants > Colors
2522
+ - Replace logos on Slide Master for all slides
2523
+ - Title slides: Use Light (300) weight Montserrat
2524
+ ` : ""}${rebrandArgs.source_format === "html" ? `
2525
+ ### HTML/Web
2526
+ - Update CSS variables or inline styles
2527
+ - Use semantic classes if available (bg-primary, text-foreground)
2528
+ - For emails: All styles must be inline
2529
+ - Include font fallback: \`'Montserrat', Arial, sans-serif\`
2530
+ ` : ""}${rebrandArgs.source_format === "generic" ? `
2531
+ ### General Approach
2532
+ 1. Identify all color values in the document
2533
+ 2. Replace using the color mapping above
2534
+ 3. Update font family and weights
2535
+ 4. Replace any logos with ${palette.name} logo
2536
+ 5. Review spacing and add whitespace where needed
2537
+ ` : ""}
2538
+
2539
+ ---
2540
+
2541
+ **Remember:** The goal is to make the document unmistakably ${palette.name} while preserving its original structure and content.
2542
+ `;
2543
+ return {
2544
+ content: [{ type: "text", text: rebrandGuide }],
2545
+ };
2546
+ }
887
2547
  default:
888
2548
  return {
889
2549
  content: [{ type: "text", text: `Unknown tool: ${name}` }],
@@ -928,6 +2588,22 @@ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
928
2588
  },
929
2589
  ],
930
2590
  },
2591
+ {
2592
+ name: "quality-check",
2593
+ description: "Evaluate UI work against the Sonance Design Excellence Framework and get an improvement plan",
2594
+ arguments: [
2595
+ {
2596
+ name: "code",
2597
+ description: "The code/component to evaluate",
2598
+ required: true,
2599
+ },
2600
+ {
2601
+ name: "description",
2602
+ description: "Brief description of what the component does",
2603
+ required: false,
2604
+ },
2605
+ ],
2606
+ },
931
2607
  ],
932
2608
  }));
933
2609
  server.setRequestHandler(GetPromptRequestSchema, async (request) => {
@@ -988,6 +2664,49 @@ Provide specific feedback on what matches and what needs to change.`,
988
2664
  ],
989
2665
  };
990
2666
  }
2667
+ if (name === "quality-check") {
2668
+ const code = promptArgs?.code || "";
2669
+ const description = promptArgs?.description || "Component";
2670
+ return {
2671
+ messages: [
2672
+ {
2673
+ role: "user",
2674
+ content: {
2675
+ type: "text",
2676
+ text: `Evaluate this ${description} against the Sonance Design Excellence Framework:
2677
+
2678
+ \`\`\`
2679
+ ${code}
2680
+ \`\`\`
2681
+
2682
+ Follow these steps:
2683
+ 1. Call evaluate_design with the code to get an automated score
2684
+ 2. Call get_anti_patterns to check for critical failures
2685
+ 3. If score < 85 (Tier 4), provide specific fixes for each gap
2686
+ 4. If score >= 85, suggest polish improvements for Tier 5
2687
+
2688
+ Framework reminder:
2689
+ - Tier 5 (95-100): Portfolio-worthy
2690
+ - Tier 4 (85-94): Professional - minimum for delivery
2691
+ - Tier 3 (70-84): Iterate before delivery
2692
+ - Tier 2 (50-69): Major iteration needed
2693
+ - Tier 1 (0-49): Rebuild from scratch
2694
+
2695
+ Scoring dimensions:
2696
+ - Brand Compliance (20%): Semantic colors, no hardcoded hex, sharp corners
2697
+ - Typography (15%): Montserrat, light/medium weights only, hierarchy
2698
+ - Color Application (10%): Dark mode support, CSS variables
2699
+ - Layout & Spacing (15%): Generous whitespace, py-20/24 sections
2700
+ - Visual Hierarchy (15%): Size and weight variation
2701
+ - Medium Excellence (15%): TypeScript, accessibility, transitions
2702
+ - Polish & Finishing (10%): Hover states, focus states
2703
+
2704
+ Provide the score, tier, and a clear improvement plan to reach Tier 4+ if needed.`,
2705
+ },
2706
+ },
2707
+ ],
2708
+ };
2709
+ }
991
2710
  throw new Error(`Unknown prompt: ${name}`);
992
2711
  });
993
2712
  // ============================================