@forgespace/branding-mcp 0.4.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/.env.example +3 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +22 -0
- package/.github/workflows/ci.yml +73 -0
- package/.github/workflows/release-automation.yml +56 -0
- package/.github/workflows/security-scan.yml +37 -0
- package/.gitleaks.toml +14 -0
- package/.prettierrc.json +10 -0
- package/CHANGELOG.md +66 -0
- package/CONTRIBUTING.md +203 -0
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/data/README.md +13 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/branding-core/ai/brand-interpreter.d.ts +5 -0
- package/dist/lib/branding-core/ai/brand-interpreter.d.ts.map +1 -0
- package/dist/lib/branding-core/ai/brand-interpreter.js +16 -0
- package/dist/lib/branding-core/ai/brand-interpreter.js.map +1 -0
- package/dist/lib/branding-core/ai/claude-interpreter.d.ts +5 -0
- package/dist/lib/branding-core/ai/claude-interpreter.d.ts.map +1 -0
- package/dist/lib/branding-core/ai/claude-interpreter.js +55 -0
- package/dist/lib/branding-core/ai/claude-interpreter.js.map +1 -0
- package/dist/lib/branding-core/ai/intent-applier.d.ts +4 -0
- package/dist/lib/branding-core/ai/intent-applier.d.ts.map +1 -0
- package/dist/lib/branding-core/ai/intent-applier.js +29 -0
- package/dist/lib/branding-core/ai/intent-applier.js.map +1 -0
- package/dist/lib/branding-core/ai/keyword-interpreter.d.ts +4 -0
- package/dist/lib/branding-core/ai/keyword-interpreter.d.ts.map +1 -0
- package/dist/lib/branding-core/ai/keyword-interpreter.js +85 -0
- package/dist/lib/branding-core/ai/keyword-interpreter.js.map +1 -0
- package/dist/lib/branding-core/ai/prompts.d.ts +4 -0
- package/dist/lib/branding-core/ai/prompts.d.ts.map +1 -0
- package/dist/lib/branding-core/ai/prompts.js +79 -0
- package/dist/lib/branding-core/ai/prompts.js.map +1 -0
- package/dist/lib/branding-core/ai/types.d.ts +27 -0
- package/dist/lib/branding-core/ai/types.d.ts.map +1 -0
- package/dist/lib/branding-core/ai/types.js +2 -0
- package/dist/lib/branding-core/ai/types.js.map +1 -0
- package/dist/lib/branding-core/documents/html-generator.d.ts +3 -0
- package/dist/lib/branding-core/documents/html-generator.d.ts.map +1 -0
- package/dist/lib/branding-core/documents/html-generator.js +31 -0
- package/dist/lib/branding-core/documents/html-generator.js.map +1 -0
- package/dist/lib/branding-core/documents/pdf-generator.d.ts +3 -0
- package/dist/lib/branding-core/documents/pdf-generator.d.ts.map +1 -0
- package/dist/lib/branding-core/documents/pdf-generator.js +20 -0
- package/dist/lib/branding-core/documents/pdf-generator.js.map +1 -0
- package/dist/lib/branding-core/exporters/css-variables.d.ts +3 -0
- package/dist/lib/branding-core/exporters/css-variables.d.ts.map +1 -0
- package/dist/lib/branding-core/exporters/css-variables.js +62 -0
- package/dist/lib/branding-core/exporters/css-variables.js.map +1 -0
- package/dist/lib/branding-core/exporters/design-tokens.d.ts +3 -0
- package/dist/lib/branding-core/exporters/design-tokens.d.ts.map +1 -0
- package/dist/lib/branding-core/exporters/design-tokens.js +75 -0
- package/dist/lib/branding-core/exporters/design-tokens.js.map +1 -0
- package/dist/lib/branding-core/exporters/figma-tokens.d.ts +9 -0
- package/dist/lib/branding-core/exporters/figma-tokens.d.ts.map +1 -0
- package/dist/lib/branding-core/exporters/figma-tokens.js +69 -0
- package/dist/lib/branding-core/exporters/figma-tokens.js.map +1 -0
- package/dist/lib/branding-core/exporters/react-theme.d.ts +3 -0
- package/dist/lib/branding-core/exporters/react-theme.d.ts.map +1 -0
- package/dist/lib/branding-core/exporters/react-theme.js +61 -0
- package/dist/lib/branding-core/exporters/react-theme.js.map +1 -0
- package/dist/lib/branding-core/exporters/sass-variables.d.ts +3 -0
- package/dist/lib/branding-core/exporters/sass-variables.d.ts.map +1 -0
- package/dist/lib/branding-core/exporters/sass-variables.js +65 -0
- package/dist/lib/branding-core/exporters/sass-variables.js.map +1 -0
- package/dist/lib/branding-core/exporters/tailwind-preset.d.ts +3 -0
- package/dist/lib/branding-core/exporters/tailwind-preset.d.ts.map +1 -0
- package/dist/lib/branding-core/exporters/tailwind-preset.js +55 -0
- package/dist/lib/branding-core/exporters/tailwind-preset.js.map +1 -0
- package/dist/lib/branding-core/generators/border-system.d.ts +3 -0
- package/dist/lib/branding-core/generators/border-system.d.ts.map +1 -0
- package/dist/lib/branding-core/generators/border-system.js +37 -0
- package/dist/lib/branding-core/generators/border-system.js.map +1 -0
- package/dist/lib/branding-core/generators/color-palette.d.ts +7 -0
- package/dist/lib/branding-core/generators/color-palette.d.ts.map +1 -0
- package/dist/lib/branding-core/generators/color-palette.js +117 -0
- package/dist/lib/branding-core/generators/color-palette.js.map +1 -0
- package/dist/lib/branding-core/generators/favicon-generator.d.ts +3 -0
- package/dist/lib/branding-core/generators/favicon-generator.d.ts.map +1 -0
- package/dist/lib/branding-core/generators/favicon-generator.js +23 -0
- package/dist/lib/branding-core/generators/favicon-generator.js.map +1 -0
- package/dist/lib/branding-core/generators/gradient-system.d.ts +3 -0
- package/dist/lib/branding-core/generators/gradient-system.d.ts.map +1 -0
- package/dist/lib/branding-core/generators/gradient-system.js +74 -0
- package/dist/lib/branding-core/generators/gradient-system.js.map +1 -0
- package/dist/lib/branding-core/generators/logo-generator.d.ts +4 -0
- package/dist/lib/branding-core/generators/logo-generator.d.ts.map +1 -0
- package/dist/lib/branding-core/generators/logo-generator.js +130 -0
- package/dist/lib/branding-core/generators/logo-generator.js.map +1 -0
- package/dist/lib/branding-core/generators/motion-system.d.ts +3 -0
- package/dist/lib/branding-core/generators/motion-system.d.ts.map +1 -0
- package/dist/lib/branding-core/generators/motion-system.js +91 -0
- package/dist/lib/branding-core/generators/motion-system.js.map +1 -0
- package/dist/lib/branding-core/generators/og-image-generator.d.ts +3 -0
- package/dist/lib/branding-core/generators/og-image-generator.d.ts.map +1 -0
- package/dist/lib/branding-core/generators/og-image-generator.js +72 -0
- package/dist/lib/branding-core/generators/og-image-generator.js.map +1 -0
- package/dist/lib/branding-core/generators/shadow-system.d.ts +3 -0
- package/dist/lib/branding-core/generators/shadow-system.d.ts.map +1 -0
- package/dist/lib/branding-core/generators/shadow-system.js +44 -0
- package/dist/lib/branding-core/generators/shadow-system.js.map +1 -0
- package/dist/lib/branding-core/generators/spacing-scale.d.ts +3 -0
- package/dist/lib/branding-core/generators/spacing-scale.d.ts.map +1 -0
- package/dist/lib/branding-core/generators/spacing-scale.js +27 -0
- package/dist/lib/branding-core/generators/spacing-scale.js.map +1 -0
- package/dist/lib/branding-core/generators/typography-system.d.ts +3 -0
- package/dist/lib/branding-core/generators/typography-system.d.ts.map +1 -0
- package/dist/lib/branding-core/generators/typography-system.js +121 -0
- package/dist/lib/branding-core/generators/typography-system.js.map +1 -0
- package/dist/lib/branding-core/index.d.ts +24 -0
- package/dist/lib/branding-core/index.d.ts.map +1 -0
- package/dist/lib/branding-core/index.js +22 -0
- package/dist/lib/branding-core/index.js.map +1 -0
- package/dist/lib/branding-core/validators/brand-consistency.d.ts +3 -0
- package/dist/lib/branding-core/validators/brand-consistency.d.ts.map +1 -0
- package/dist/lib/branding-core/validators/brand-consistency.js +70 -0
- package/dist/lib/branding-core/validators/brand-consistency.js.map +1 -0
- package/dist/lib/branding-core/validators/contrast-checker.d.ts +3 -0
- package/dist/lib/branding-core/validators/contrast-checker.d.ts.map +1 -0
- package/dist/lib/branding-core/validators/contrast-checker.js +33 -0
- package/dist/lib/branding-core/validators/contrast-checker.js.map +1 -0
- package/dist/lib/branding-core/validators/token-schema.d.ts +10 -0
- package/dist/lib/branding-core/validators/token-schema.d.ts.map +1 -0
- package/dist/lib/branding-core/validators/token-schema.js +43 -0
- package/dist/lib/branding-core/validators/token-schema.js.map +1 -0
- package/dist/lib/config.d.ts +7 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +8 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/logger.d.ts +3 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +10 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/types.d.ts +208 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/resources/brand-knowledge.d.ts +3 -0
- package/dist/resources/brand-knowledge.d.ts.map +1 -0
- package/dist/resources/brand-knowledge.js +53 -0
- package/dist/resources/brand-knowledge.js.map +1 -0
- package/dist/resources/brand-templates.d.ts +3 -0
- package/dist/resources/brand-templates.d.ts.map +1 -0
- package/dist/resources/brand-templates.js +68 -0
- package/dist/resources/brand-templates.js.map +1 -0
- package/dist/tools/create-brand-guidelines.d.ts +3 -0
- package/dist/tools/create-brand-guidelines.d.ts.map +1 -0
- package/dist/tools/create-brand-guidelines.js +85 -0
- package/dist/tools/create-brand-guidelines.js.map +1 -0
- package/dist/tools/export-design-tokens.d.ts +3 -0
- package/dist/tools/export-design-tokens.d.ts.map +1 -0
- package/dist/tools/export-design-tokens.js +37 -0
- package/dist/tools/export-design-tokens.js.map +1 -0
- package/dist/tools/generate-brand-assets.d.ts +3 -0
- package/dist/tools/generate-brand-assets.d.ts.map +1 -0
- package/dist/tools/generate-brand-assets.js +37 -0
- package/dist/tools/generate-brand-assets.js.map +1 -0
- package/dist/tools/generate-brand-identity.d.ts +3 -0
- package/dist/tools/generate-brand-identity.d.ts.map +1 -0
- package/dist/tools/generate-brand-identity.js +73 -0
- package/dist/tools/generate-brand-identity.js.map +1 -0
- package/dist/tools/generate-color-palette.d.ts +3 -0
- package/dist/tools/generate-color-palette.d.ts.map +1 -0
- package/dist/tools/generate-color-palette.js +33 -0
- package/dist/tools/generate-color-palette.js.map +1 -0
- package/dist/tools/generate-typography-system.d.ts +3 -0
- package/dist/tools/generate-typography-system.d.ts.map +1 -0
- package/dist/tools/generate-typography-system.js +28 -0
- package/dist/tools/generate-typography-system.js.map +1 -0
- package/dist/tools/refine-brand-element.d.ts +3 -0
- package/dist/tools/refine-brand-element.d.ts.map +1 -0
- package/dist/tools/refine-brand-element.js +41 -0
- package/dist/tools/refine-brand-element.js.map +1 -0
- package/dist/tools/validate-brand-consistency.d.ts +3 -0
- package/dist/tools/validate-brand-consistency.d.ts.map +1 -0
- package/dist/tools/validate-brand-consistency.js +25 -0
- package/dist/tools/validate-brand-consistency.js.map +1 -0
- package/docs/API.md +110 -0
- package/docs/DATA_SOURCES.md +69 -0
- package/docs/INTEGRATION.md +58 -0
- package/eslint.config.js +52 -0
- package/jest.config.js +40 -0
- package/package.json +78 -0
- package/src/__tests__/integration/brand-generation.test.ts +84 -0
- package/src/__tests__/integration/mcp-server.test.ts +18 -0
- package/src/__tests__/unit/ai-interpreter.test.ts +172 -0
- package/src/__tests__/unit/border-system.test.ts +77 -0
- package/src/__tests__/unit/color-palette.test.ts +161 -0
- package/src/__tests__/unit/contrast-checker.test.ts +124 -0
- package/src/__tests__/unit/design-tokens.test.ts +184 -0
- package/src/__tests__/unit/favicon-generator.test.ts +80 -0
- package/src/__tests__/unit/gradient-system.test.ts +122 -0
- package/src/__tests__/unit/logo-generator.test.ts +146 -0
- package/src/__tests__/unit/motion-system.test.ts +91 -0
- package/src/__tests__/unit/og-image-generator.test.ts +115 -0
- package/src/__tests__/unit/shadow-system.test.ts +63 -0
- package/src/__tests__/unit/spacing-scale.test.ts +60 -0
- package/src/__tests__/unit/typography-system.test.ts +71 -0
- package/src/index.ts +59 -0
- package/src/lib/branding-core/ai/brand-interpreter.ts +30 -0
- package/src/lib/branding-core/ai/claude-interpreter.ts +76 -0
- package/src/lib/branding-core/ai/intent-applier.ts +59 -0
- package/src/lib/branding-core/ai/keyword-interpreter.ts +95 -0
- package/src/lib/branding-core/ai/prompts.ts +93 -0
- package/src/lib/branding-core/ai/types.ts +36 -0
- package/src/lib/branding-core/documents/html-generator.ts +32 -0
- package/src/lib/branding-core/documents/pdf-generator.ts +21 -0
- package/src/lib/branding-core/exporters/css-variables.ts +71 -0
- package/src/lib/branding-core/exporters/design-tokens.ts +86 -0
- package/src/lib/branding-core/exporters/figma-tokens.ts +87 -0
- package/src/lib/branding-core/exporters/react-theme.ts +69 -0
- package/src/lib/branding-core/exporters/sass-variables.ts +74 -0
- package/src/lib/branding-core/exporters/tailwind-preset.ts +67 -0
- package/src/lib/branding-core/generators/border-system.ts +41 -0
- package/src/lib/branding-core/generators/color-palette.ts +147 -0
- package/src/lib/branding-core/generators/favicon-generator.ts +33 -0
- package/src/lib/branding-core/generators/gradient-system.ts +120 -0
- package/src/lib/branding-core/generators/logo-generator.ts +152 -0
- package/src/lib/branding-core/generators/motion-system.ts +98 -0
- package/src/lib/branding-core/generators/og-image-generator.ts +97 -0
- package/src/lib/branding-core/generators/shadow-system.ts +66 -0
- package/src/lib/branding-core/generators/spacing-scale.ts +29 -0
- package/src/lib/branding-core/generators/typography-system.ts +128 -0
- package/src/lib/branding-core/index.ts +28 -0
- package/src/lib/branding-core/validators/brand-consistency.ts +79 -0
- package/src/lib/branding-core/validators/contrast-checker.ts +37 -0
- package/src/lib/branding-core/validators/token-schema.ts +50 -0
- package/src/lib/config.ts +13 -0
- package/src/lib/logger.ts +12 -0
- package/src/lib/types.ts +236 -0
- package/src/resources/brand-knowledge.ts +60 -0
- package/src/resources/brand-templates.ts +70 -0
- package/src/tools/create-brand-guidelines.ts +94 -0
- package/src/tools/export-design-tokens.ts +52 -0
- package/src/tools/generate-brand-assets.ts +48 -0
- package/src/tools/generate-brand-identity.ts +115 -0
- package/src/tools/generate-color-palette.ts +43 -0
- package/src/tools/generate-typography-system.ts +42 -0
- package/src/tools/refine-brand-element.ts +65 -0
- package/src/tools/validate-brand-consistency.ts +32 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { interpretWithKeywords } from '../../lib/branding-core/ai/keyword-interpreter.js';
|
|
2
|
+
import { applyIntent } from '../../lib/branding-core/ai/intent-applier.js';
|
|
3
|
+
import { interpretFeedback } from '../../lib/branding-core/ai/brand-interpreter.js';
|
|
4
|
+
import { generateColorPalette } from '../../lib/branding-core/generators/color-palette.js';
|
|
5
|
+
import { generateTypographySystem } from '../../lib/branding-core/generators/typography-system.js';
|
|
6
|
+
import { generateSpacingScale } from '../../lib/branding-core/generators/spacing-scale.js';
|
|
7
|
+
|
|
8
|
+
function createTestBrand() {
|
|
9
|
+
return {
|
|
10
|
+
id: 'test_brand',
|
|
11
|
+
name: 'Test',
|
|
12
|
+
industry: 'tech',
|
|
13
|
+
style: 'tech' as const,
|
|
14
|
+
colors: generateColorPalette('#6B4CE6'),
|
|
15
|
+
typography: generateTypographySystem(),
|
|
16
|
+
spacing: generateSpacingScale(),
|
|
17
|
+
createdAt: '2026-01-01T00:00:00Z',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('interpretWithKeywords', () => {
|
|
22
|
+
it('interprets "warm" as analogous with positive hue shift', () => {
|
|
23
|
+
const intent = interpretWithKeywords('make it warmer', 'colors');
|
|
24
|
+
expect(intent.color?.harmony).toBe('analogous');
|
|
25
|
+
expect(intent.color?.hueShift).toBeGreaterThan(0);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('interprets "vibrant" as triadic with saturation boost', () => {
|
|
29
|
+
const intent = interpretWithKeywords('more vibrant colors', 'colors');
|
|
30
|
+
expect(intent.color?.harmony).toBe('triadic');
|
|
31
|
+
expect(intent.color?.saturationShift).toBeGreaterThan(0);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('interprets "muted" as monochromatic with reduced saturation', () => {
|
|
35
|
+
const intent = interpretWithKeywords('make it muted', 'colors');
|
|
36
|
+
expect(intent.color?.harmony).toBe('monochromatic');
|
|
37
|
+
expect(intent.color?.saturationShift).toBeLessThan(0);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('interprets "premium" as analogous with lower saturation and lightness', () => {
|
|
41
|
+
const intent = interpretWithKeywords('premium feel', 'colors');
|
|
42
|
+
expect(intent.color?.harmony).toBe('analogous');
|
|
43
|
+
expect(intent.color?.saturationShift).toBeLessThan(0);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('interprets "dark" theme', () => {
|
|
47
|
+
const intent = interpretWithKeywords('make it darker', 'colors');
|
|
48
|
+
expect(intent.color?.theme).toBe('dark');
|
|
49
|
+
expect(intent.color?.lightnessShift).toBeLessThan(0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('falls back to complementary with no matching keywords', () => {
|
|
53
|
+
const intent = interpretWithKeywords('xyzzy', 'colors');
|
|
54
|
+
expect(intent.color?.harmony).toBe('complementary');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('interprets "serif" for typography', () => {
|
|
58
|
+
const intent = interpretWithKeywords('use serif headings', 'typography');
|
|
59
|
+
expect(intent.typography?.headingCategory).toBe('serif');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('interprets "monospace" for typography body', () => {
|
|
63
|
+
const intent = interpretWithKeywords('monospace body text', 'typography');
|
|
64
|
+
expect(intent.typography?.bodyCategory).toBe('monospace');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('interprets "dramatic" scale for typography', () => {
|
|
68
|
+
const intent = interpretWithKeywords('dramatic type scale', 'typography');
|
|
69
|
+
expect(intent.typography?.scaleRatio).toBe('golden-ratio');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('interprets "editorial" as serif + perfect-fourth', () => {
|
|
73
|
+
const intent = interpretWithKeywords('editorial style', 'typography');
|
|
74
|
+
expect(intent.typography?.headingCategory).toBe('serif');
|
|
75
|
+
expect(intent.typography?.scaleRatio).toBe('perfect-fourth');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('interprets "modern" as sans-serif heading and body', () => {
|
|
79
|
+
const intent = interpretWithKeywords('modern look', 'typography');
|
|
80
|
+
expect(intent.typography?.headingCategory).toBe('sans-serif');
|
|
81
|
+
expect(intent.typography?.bodyCategory).toBe('sans-serif');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('falls back for typography with no matching keywords', () => {
|
|
85
|
+
const intent = interpretWithKeywords('xyzzy', 'typography');
|
|
86
|
+
expect(intent.typography?.headingCategory).toBe('sans-serif');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('returns reasoning for spacing with no actions', () => {
|
|
90
|
+
const intent = interpretWithKeywords('more space', 'spacing');
|
|
91
|
+
expect(intent.reasoning).toBeDefined();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('includes reasoning string', () => {
|
|
95
|
+
const intent = interpretWithKeywords('warm and vibrant', 'colors');
|
|
96
|
+
expect(intent.reasoning).toContain('Keyword match');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('merges multiple color keywords', () => {
|
|
100
|
+
const intent = interpretWithKeywords('warm and bold', 'colors');
|
|
101
|
+
expect(intent.color?.saturationShift).toBeGreaterThan(0);
|
|
102
|
+
expect(intent.color?.hueShift).toBeGreaterThan(0);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('applyIntent', () => {
|
|
107
|
+
it('applies color intent to generate new palette', () => {
|
|
108
|
+
const brand = createTestBrand();
|
|
109
|
+
const updated = applyIntent(brand, {
|
|
110
|
+
color: { harmony: 'triadic', saturationShift: 10 },
|
|
111
|
+
});
|
|
112
|
+
expect(updated.colors).toBeDefined();
|
|
113
|
+
expect(updated.colors.primary.hex).not.toBe(brand.colors.primary.hex);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('applies typography intent', () => {
|
|
117
|
+
const brand = createTestBrand();
|
|
118
|
+
const updated = applyIntent(brand, {
|
|
119
|
+
typography: { headingCategory: 'serif', scaleRatio: 'perfect-fourth' },
|
|
120
|
+
});
|
|
121
|
+
expect(updated.typography.scaleRatio).toBe(1.333);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('applies style change', () => {
|
|
125
|
+
const brand = createTestBrand();
|
|
126
|
+
const updated = applyIntent(brand, { style: 'elegant' });
|
|
127
|
+
expect(updated.style).toBe('elegant');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('applies hue shift to base color', () => {
|
|
131
|
+
const brand = createTestBrand();
|
|
132
|
+
const updated = applyIntent(brand, {
|
|
133
|
+
color: { hueShift: 30 },
|
|
134
|
+
});
|
|
135
|
+
expect(updated.colors.primary.hex).toBeDefined();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('preserves unchanged elements', () => {
|
|
139
|
+
const brand = createTestBrand();
|
|
140
|
+
const updated = applyIntent(brand, { color: { harmony: 'analogous' } });
|
|
141
|
+
expect(updated.typography).toEqual(brand.typography);
|
|
142
|
+
expect(updated.spacing).toEqual(brand.spacing);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('returns brand unchanged with empty intent', () => {
|
|
146
|
+
const brand = createTestBrand();
|
|
147
|
+
const updated = applyIntent(brand, {});
|
|
148
|
+
expect(updated.colors).toEqual(brand.colors);
|
|
149
|
+
expect(updated.typography).toEqual(brand.typography);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('interpretFeedback (strategy selection)', () => {
|
|
154
|
+
it('uses keyword strategy when no API key', async () => {
|
|
155
|
+
const brand = createTestBrand();
|
|
156
|
+
const intent = await interpretFeedback('warm colors', 'colors', brand, 'auto');
|
|
157
|
+
expect(intent.color?.harmony).toBe('analogous');
|
|
158
|
+
expect(intent.reasoning).toContain('Keyword');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('uses keyword strategy when explicitly set', async () => {
|
|
162
|
+
const brand = createTestBrand();
|
|
163
|
+
const intent = await interpretFeedback('warm colors', 'colors', brand, 'keyword');
|
|
164
|
+
expect(intent.color?.harmony).toBe('analogous');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('falls back to keywords when ai strategy has no key', async () => {
|
|
168
|
+
const brand = createTestBrand();
|
|
169
|
+
const intent = await interpretFeedback('warm', 'colors', brand, 'ai', {});
|
|
170
|
+
expect(intent.reasoning).toContain('Keyword');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { generateBorderSystem } from '../../lib/branding-core/generators/border-system.js';
|
|
2
|
+
import type { BrandStyle } from '../../lib/types.js';
|
|
3
|
+
|
|
4
|
+
describe('generateBorderSystem', () => {
|
|
5
|
+
it('generates all radius levels', () => {
|
|
6
|
+
const system = generateBorderSystem();
|
|
7
|
+
const names = Object.keys(system.radii);
|
|
8
|
+
expect(names).toEqual(['none', 'sm', 'md', 'lg', 'xl', 'full', 'circle']);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('none radius is always 0px', () => {
|
|
12
|
+
const system = generateBorderSystem('playful');
|
|
13
|
+
expect(system.radii.none).toBe('0px');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('full and circle are always 9999px', () => {
|
|
17
|
+
const styles: BrandStyle[] = ['minimal', 'bold', 'elegant', 'playful'];
|
|
18
|
+
for (const style of styles) {
|
|
19
|
+
const system = generateBorderSystem(style);
|
|
20
|
+
expect(system.radii.full).toBe('9999px');
|
|
21
|
+
expect(system.radii.circle).toBe('9999px');
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('playful has larger radii than corporate', () => {
|
|
26
|
+
const playful = generateBorderSystem('playful');
|
|
27
|
+
const corporate = generateBorderSystem('corporate');
|
|
28
|
+
expect(parseInt(playful.radii.md)).toBeGreaterThan(parseInt(corporate.radii.md));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('generates three border widths', () => {
|
|
32
|
+
const system = generateBorderSystem();
|
|
33
|
+
expect(system.widths.thin).toBeDefined();
|
|
34
|
+
expect(system.widths.medium).toBeDefined();
|
|
35
|
+
expect(system.widths.thick).toBeDefined();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('border widths increase thin < medium < thick', () => {
|
|
39
|
+
const system = generateBorderSystem('bold');
|
|
40
|
+
const thin = parseInt(system.widths.thin);
|
|
41
|
+
const medium = parseInt(system.widths.medium);
|
|
42
|
+
const thick = parseInt(system.widths.thick);
|
|
43
|
+
expect(thin).toBeLessThanOrEqual(medium);
|
|
44
|
+
expect(medium).toBeLessThanOrEqual(thick);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('bold style has thicker borders', () => {
|
|
48
|
+
const bold = generateBorderSystem('bold');
|
|
49
|
+
const minimal = generateBorderSystem('minimal');
|
|
50
|
+
expect(parseInt(bold.widths.thick)).toBeGreaterThanOrEqual(parseInt(minimal.widths.thick));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('radii values are formatted as px strings', () => {
|
|
54
|
+
const system = generateBorderSystem();
|
|
55
|
+
for (const value of Object.values(system.radii)) {
|
|
56
|
+
expect(value).toMatch(/^\d+px$/);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('works for all brand styles', () => {
|
|
61
|
+
const styles: BrandStyle[] = [
|
|
62
|
+
'minimal',
|
|
63
|
+
'bold',
|
|
64
|
+
'elegant',
|
|
65
|
+
'playful',
|
|
66
|
+
'corporate',
|
|
67
|
+
'tech',
|
|
68
|
+
'organic',
|
|
69
|
+
'retro',
|
|
70
|
+
];
|
|
71
|
+
for (const style of styles) {
|
|
72
|
+
const system = generateBorderSystem(style);
|
|
73
|
+
expect(Object.keys(system.radii)).toHaveLength(7);
|
|
74
|
+
expect(Object.keys(system.widths)).toHaveLength(3);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateColorPalette,
|
|
3
|
+
checkContrast,
|
|
4
|
+
hslToHex,
|
|
5
|
+
hexToHsl,
|
|
6
|
+
} from '../../lib/branding-core/generators/color-palette.js';
|
|
7
|
+
|
|
8
|
+
describe('hslToHex', () => {
|
|
9
|
+
it('converts pure red', () => {
|
|
10
|
+
expect(hslToHex(0, 100, 50)).toBe('#ff0000');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('converts pure green', () => {
|
|
14
|
+
expect(hslToHex(120, 100, 50)).toBe('#00ff00');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('converts pure blue', () => {
|
|
18
|
+
expect(hslToHex(240, 100, 50)).toBe('#0000ff');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('converts white', () => {
|
|
22
|
+
expect(hslToHex(0, 0, 100)).toBe('#ffffff');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('converts black', () => {
|
|
26
|
+
expect(hslToHex(0, 0, 0)).toBe('#000000');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('handles negative hue via wrapping', () => {
|
|
30
|
+
const result = hslToHex(-60, 100, 50);
|
|
31
|
+
expect(result).toMatch(/^#[0-9a-f]{6}$/);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('hexToHsl', () => {
|
|
36
|
+
it('converts red hex to HSL', () => {
|
|
37
|
+
const hsl = hexToHsl('#ff0000');
|
|
38
|
+
expect(hsl.h).toBe(0);
|
|
39
|
+
expect(hsl.s).toBe(100);
|
|
40
|
+
expect(hsl.l).toBe(50);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('converts white hex to HSL', () => {
|
|
44
|
+
const hsl = hexToHsl('#ffffff');
|
|
45
|
+
expect(hsl.l).toBe(100);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('handles invalid hex gracefully', () => {
|
|
49
|
+
const hsl = hexToHsl('invalid');
|
|
50
|
+
expect(hsl).toEqual({ h: 0, s: 0, l: 0 });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('handles hex without hash', () => {
|
|
54
|
+
const hsl = hexToHsl('ff0000');
|
|
55
|
+
expect(hsl.h).toBe(0);
|
|
56
|
+
expect(hsl.s).toBe(100);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('checkContrast', () => {
|
|
61
|
+
it('returns maximum contrast for black on white', () => {
|
|
62
|
+
const result = checkContrast('#000000', '#ffffff');
|
|
63
|
+
expect(result.ratio).toBeGreaterThan(20);
|
|
64
|
+
expect(result.aa).toBe(true);
|
|
65
|
+
expect(result.aaa).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('returns minimum contrast for same color', () => {
|
|
69
|
+
const result = checkContrast('#808080', '#808080');
|
|
70
|
+
expect(result.ratio).toBe(1);
|
|
71
|
+
expect(result.aa).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('correctly identifies AA pass for dark blue on white', () => {
|
|
75
|
+
const result = checkContrast('#003366', '#ffffff');
|
|
76
|
+
expect(result.aa).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('correctly identifies AA failure for light gray on white', () => {
|
|
80
|
+
const result = checkContrast('#cccccc', '#ffffff');
|
|
81
|
+
expect(result.aa).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('generateColorPalette', () => {
|
|
86
|
+
it('generates a complete palette with defaults', () => {
|
|
87
|
+
const palette = generateColorPalette();
|
|
88
|
+
expect(palette.primary).toBeDefined();
|
|
89
|
+
expect(palette.secondary).toBeDefined();
|
|
90
|
+
expect(palette.accent).toBeDefined();
|
|
91
|
+
expect(palette.neutral.length).toBeGreaterThanOrEqual(4);
|
|
92
|
+
expect(palette.semantic.success).toBeDefined();
|
|
93
|
+
expect(palette.semantic.warning).toBeDefined();
|
|
94
|
+
expect(palette.semantic.error).toBeDefined();
|
|
95
|
+
expect(palette.semantic.info).toBeDefined();
|
|
96
|
+
expect(palette.contrast).toBeDefined();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('generates palette from a base color', () => {
|
|
100
|
+
const palette = generateColorPalette('#6B4CE6');
|
|
101
|
+
expect(palette.primary.hex).toMatch(/^#[0-9a-f]{6}$/);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('generates complementary palette', () => {
|
|
105
|
+
const palette = generateColorPalette('#0000ff', 'complementary');
|
|
106
|
+
expect(palette.primary).toBeDefined();
|
|
107
|
+
expect(palette.secondary).toBeDefined();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('generates analogous palette', () => {
|
|
111
|
+
const palette = generateColorPalette('#0000ff', 'analogous');
|
|
112
|
+
expect(palette.primary).toBeDefined();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('generates triadic palette', () => {
|
|
116
|
+
const palette = generateColorPalette('#0000ff', 'triadic');
|
|
117
|
+
expect(palette.primary).toBeDefined();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('generates monochromatic palette', () => {
|
|
121
|
+
const palette = generateColorPalette('#6B4CE6', 'monochromatic');
|
|
122
|
+
expect(palette.primary).toBeDefined();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('generates dark theme neutrals', () => {
|
|
126
|
+
const palette = generateColorPalette('#6B4CE6', 'complementary', 'dark');
|
|
127
|
+
expect(palette.neutral.length).toBeGreaterThanOrEqual(4);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('includes contrast data', () => {
|
|
131
|
+
const palette = generateColorPalette();
|
|
132
|
+
expect(palette.contrast['primary-on-white']).toBeDefined();
|
|
133
|
+
expect(palette.contrast['primary-on-white'].ratio).toBeGreaterThan(0);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('generates split-complementary palette', () => {
|
|
137
|
+
const palette = generateColorPalette('#0000ff', 'split-complementary');
|
|
138
|
+
expect(palette.primary).toBeDefined();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('generates tetradic palette', () => {
|
|
142
|
+
const palette = generateColorPalette('#0000ff', 'tetradic');
|
|
143
|
+
expect(palette.primary).toBeDefined();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('generates light theme neutrals', () => {
|
|
147
|
+
const palette = generateColorPalette('#6B4CE6', 'complementary', 'light');
|
|
148
|
+
expect(palette.neutral.length).toBeGreaterThanOrEqual(4);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('produces valid hex colors for all swatches', () => {
|
|
152
|
+
const palette = generateColorPalette('#FF6B35', 'triadic');
|
|
153
|
+
const hexRegex = /^#[0-9a-f]{6}$/;
|
|
154
|
+
expect(palette.primary.hex).toMatch(hexRegex);
|
|
155
|
+
expect(palette.secondary.hex).toMatch(hexRegex);
|
|
156
|
+
expect(palette.accent.hex).toMatch(hexRegex);
|
|
157
|
+
for (const n of palette.neutral) {
|
|
158
|
+
expect(n.hex).toMatch(hexRegex);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { validateContrast } from '../../lib/branding-core/validators/contrast-checker.js';
|
|
2
|
+
import { validateBrandConsistency } from '../../lib/branding-core/validators/brand-consistency.js';
|
|
3
|
+
import { generateColorPalette } from '../../lib/branding-core/generators/color-palette.js';
|
|
4
|
+
import { generateTypographySystem } from '../../lib/branding-core/generators/typography-system.js';
|
|
5
|
+
import { generateSpacingScale } from '../../lib/branding-core/generators/spacing-scale.js';
|
|
6
|
+
import type { BrandIdentity } from '../../lib/types.js';
|
|
7
|
+
|
|
8
|
+
function createTestBrand(overrides?: Partial<BrandIdentity>): BrandIdentity {
|
|
9
|
+
return {
|
|
10
|
+
id: 'test_brand',
|
|
11
|
+
name: 'Test',
|
|
12
|
+
industry: 'tech',
|
|
13
|
+
style: 'tech',
|
|
14
|
+
colors: generateColorPalette('#003366'),
|
|
15
|
+
typography: generateTypographySystem(),
|
|
16
|
+
spacing: generateSpacingScale(),
|
|
17
|
+
createdAt: '2026-01-01T00:00:00Z',
|
|
18
|
+
...overrides,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('validateContrast', () => {
|
|
23
|
+
it('returns no issues for high-contrast colors', () => {
|
|
24
|
+
const brand = createTestBrand({ colors: generateColorPalette('#003366') });
|
|
25
|
+
const issues = validateContrast(brand);
|
|
26
|
+
const errors = issues.filter((i) => i.severity === 'error');
|
|
27
|
+
expect(errors.length).toBe(0);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('flags low-contrast colors', () => {
|
|
31
|
+
const brand = createTestBrand({ colors: generateColorPalette('#cccccc') });
|
|
32
|
+
const issues = validateContrast(brand);
|
|
33
|
+
expect(issues.length).toBeGreaterThan(0);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('validateBrandConsistency', () => {
|
|
38
|
+
it('validates a complete brand as valid', () => {
|
|
39
|
+
const brand = createTestBrand();
|
|
40
|
+
const result = validateBrandConsistency(brand);
|
|
41
|
+
expect(result.score).toBeGreaterThan(0);
|
|
42
|
+
expect(typeof result.valid).toBe('boolean');
|
|
43
|
+
expect(Array.isArray(result.issues)).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('includes contrast issues in overall validation', () => {
|
|
47
|
+
const brand = createTestBrand({ colors: generateColorPalette('#cccccc') });
|
|
48
|
+
const result = validateBrandConsistency(brand);
|
|
49
|
+
expect(result.issues.length).toBeGreaterThan(0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('validates typography properties', () => {
|
|
53
|
+
const brand = createTestBrand();
|
|
54
|
+
const result = validateBrandConsistency(brand);
|
|
55
|
+
const typographyIssues = result.issues.filter((i) => i.element.startsWith('typography'));
|
|
56
|
+
expect(typographyIssues.every((i) => i.severity !== 'error')).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('returns score between 0 and 100', () => {
|
|
60
|
+
const brand = createTestBrand();
|
|
61
|
+
const result = validateBrandConsistency(brand);
|
|
62
|
+
expect(result.score).toBeGreaterThanOrEqual(0);
|
|
63
|
+
expect(result.score).toBeLessThanOrEqual(100);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('flags missing primary color', () => {
|
|
67
|
+
const brand = createTestBrand();
|
|
68
|
+
brand.colors.primary.hex = '';
|
|
69
|
+
const result = validateBrandConsistency(brand);
|
|
70
|
+
expect(result.issues.some((i) => i.element === 'color.primary')).toBe(true);
|
|
71
|
+
expect(result.valid).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('flags missing secondary color', () => {
|
|
75
|
+
const brand = createTestBrand();
|
|
76
|
+
brand.colors.secondary.hex = '';
|
|
77
|
+
const result = validateBrandConsistency(brand);
|
|
78
|
+
expect(result.issues.some((i) => i.element === 'color.secondary')).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('flags too few neutral shades', () => {
|
|
82
|
+
const brand = createTestBrand();
|
|
83
|
+
brand.colors.neutral = brand.colors.neutral.slice(0, 2);
|
|
84
|
+
const result = validateBrandConsistency(brand);
|
|
85
|
+
expect(result.issues.some((i) => i.element === 'color.neutral')).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('flags missing heading font', () => {
|
|
89
|
+
const brand = createTestBrand();
|
|
90
|
+
brand.typography.headingFont = '';
|
|
91
|
+
const result = validateBrandConsistency(brand);
|
|
92
|
+
expect(result.issues.some((i) => i.element === 'typography.headingFont')).toBe(true);
|
|
93
|
+
expect(result.valid).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('flags missing body font', () => {
|
|
97
|
+
const brand = createTestBrand();
|
|
98
|
+
brand.typography.bodyFont = '';
|
|
99
|
+
const result = validateBrandConsistency(brand);
|
|
100
|
+
expect(result.issues.some((i) => i.element === 'typography.bodyFont')).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('flags base size outside recommended range', () => {
|
|
104
|
+
const brand = createTestBrand();
|
|
105
|
+
brand.typography.baseSize = 10;
|
|
106
|
+
const result = validateBrandConsistency(brand);
|
|
107
|
+
expect(result.issues.some((i) => i.element === 'typography.baseSize')).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('flags base size above recommended range', () => {
|
|
111
|
+
const brand = createTestBrand();
|
|
112
|
+
brand.typography.baseSize = 24;
|
|
113
|
+
const result = validateBrandConsistency(brand);
|
|
114
|
+
expect(result.issues.some((i) => i.element === 'typography.baseSize')).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('reduces score for errors more than warnings', () => {
|
|
118
|
+
const brand = createTestBrand();
|
|
119
|
+
brand.colors.primary.hex = '';
|
|
120
|
+
brand.colors.secondary.hex = '';
|
|
121
|
+
const result = validateBrandConsistency(brand);
|
|
122
|
+
expect(result.score).toBeLessThan(70);
|
|
123
|
+
});
|
|
124
|
+
});
|