@gemini-designer/mcp-server 0.1.2 → 0.1.29

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 (129) hide show
  1. package/dist/components/catalog.d.ts.map +1 -1
  2. package/dist/components/catalog.js +10 -4
  3. package/dist/components/catalog.js.map +1 -1
  4. package/dist/config/index.d.ts.map +1 -1
  5. package/dist/config/index.js +11 -6
  6. package/dist/config/index.js.map +1 -1
  7. package/dist/context/builder.d.ts.map +1 -1
  8. package/dist/context/builder.js.map +1 -1
  9. package/dist/context/filter.d.ts.map +1 -1
  10. package/dist/context/filter.js +5 -1
  11. package/dist/context/filter.js.map +1 -1
  12. package/dist/context/grounding.d.ts.map +1 -1
  13. package/dist/context/grounding.js +7 -3
  14. package/dist/context/grounding.js.map +1 -1
  15. package/dist/context/guards.d.ts.map +1 -1
  16. package/dist/context/guards.js +53 -0
  17. package/dist/context/guards.js.map +1 -1
  18. package/dist/context/repo-hints.js.map +1 -1
  19. package/dist/context/styling-detector.d.ts +24 -0
  20. package/dist/context/styling-detector.d.ts.map +1 -0
  21. package/dist/context/styling-detector.js +337 -0
  22. package/dist/context/styling-detector.js.map +1 -0
  23. package/dist/design/principles.js.map +1 -1
  24. package/dist/generation/gemini-client.d.ts.map +1 -1
  25. package/dist/generation/gemini-client.js.map +1 -1
  26. package/dist/generation/litellm-client.d.ts.map +1 -1
  27. package/dist/generation/litellm-client.js +14 -7
  28. package/dist/generation/litellm-client.js.map +1 -1
  29. package/dist/generation/remote-client.d.ts +10 -5
  30. package/dist/generation/remote-client.d.ts.map +1 -1
  31. package/dist/generation/remote-client.js +13 -2
  32. package/dist/generation/remote-client.js.map +1 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/output/file-writer.d.ts.map +1 -1
  35. package/dist/output/file-writer.js +4 -4
  36. package/dist/output/file-writer.js.map +1 -1
  37. package/dist/output/formatter.d.ts.map +1 -1
  38. package/dist/output/formatter.js +5 -2
  39. package/dist/output/formatter.js.map +1 -1
  40. package/dist/server.d.ts.map +1 -1
  41. package/dist/server.js +2 -1
  42. package/dist/server.js.map +1 -1
  43. package/dist/stack/detect.d.ts.map +1 -1
  44. package/dist/stack/detect.js +42 -9
  45. package/dist/stack/detect.js.map +1 -1
  46. package/dist/tokens/sync.d.ts.map +1 -1
  47. package/dist/tokens/sync.js +22 -5
  48. package/dist/tokens/sync.js.map +1 -1
  49. package/dist/tools/analyze-screenshot-ui.d.ts.map +1 -1
  50. package/dist/tools/analyze-screenshot-ui.js +5 -5
  51. package/dist/tools/analyze-screenshot-ui.js.map +1 -1
  52. package/dist/tools/analyze-tokens.d.ts.map +1 -1
  53. package/dist/tools/analyze-tokens.js +3 -1
  54. package/dist/tools/analyze-tokens.js.map +1 -1
  55. package/dist/tools/catalog-components.d.ts.map +1 -1
  56. package/dist/tools/catalog-components.js +1 -4
  57. package/dist/tools/catalog-components.js.map +1 -1
  58. package/dist/tools/create-ui.d.ts +3 -0
  59. package/dist/tools/create-ui.d.ts.map +1 -1
  60. package/dist/tools/create-ui.js +203 -75
  61. package/dist/tools/create-ui.js.map +1 -1
  62. package/dist/tools/detect-ui-stack.js.map +1 -1
  63. package/dist/tools/generate-component-variants.d.ts.map +1 -1
  64. package/dist/tools/generate-component-variants.js +15 -4
  65. package/dist/tools/generate-component-variants.js.map +1 -1
  66. package/dist/tools/generate-vibes.d.ts.map +1 -1
  67. package/dist/tools/generate-vibes.js +7 -3
  68. package/dist/tools/generate-vibes.js.map +1 -1
  69. package/dist/tools/index.js.map +1 -1
  70. package/dist/tools/modify-ui.d.ts.map +1 -1
  71. package/dist/tools/modify-ui.js +7 -2
  72. package/dist/tools/modify-ui.js.map +1 -1
  73. package/dist/tools/scaffold-project.d.ts.map +1 -1
  74. package/dist/tools/scaffold-project.js +3 -1
  75. package/dist/tools/scaffold-project.js.map +1 -1
  76. package/dist/tools/snippet-ui.d.ts +3 -1
  77. package/dist/tools/snippet-ui.d.ts.map +1 -1
  78. package/dist/tools/snippet-ui.js +219 -88
  79. package/dist/tools/snippet-ui.js.map +1 -1
  80. package/dist/tools/sync-design-tokens.d.ts.map +1 -1
  81. package/dist/tools/sync-design-tokens.js +26 -11
  82. package/dist/tools/sync-design-tokens.js.map +1 -1
  83. package/dist/utils/walk.d.ts.map +1 -1
  84. package/dist/utils/walk.js.map +1 -1
  85. package/dist/version.d.ts +2 -0
  86. package/dist/version.d.ts.map +1 -0
  87. package/dist/version.js +5 -0
  88. package/dist/version.js.map +1 -0
  89. package/package.json +55 -55
  90. package/src/__tests__/builder.test.ts +19 -19
  91. package/src/__tests__/config.test.ts +63 -31
  92. package/src/__tests__/filter.test.ts +98 -92
  93. package/src/__tests__/remote-client.test.ts +179 -0
  94. package/src/components/catalog.ts +170 -166
  95. package/src/config/index.ts +185 -177
  96. package/src/context/builder.ts +157 -157
  97. package/src/context/filter.ts +110 -104
  98. package/src/context/grounding.ts +143 -129
  99. package/src/context/guards.ts +97 -38
  100. package/src/context/repo-hints.ts +24 -24
  101. package/src/context/styling-detector.ts +460 -0
  102. package/src/design/principles.ts +14 -14
  103. package/src/generation/gemini-client.ts +53 -56
  104. package/src/generation/litellm-client.ts +102 -86
  105. package/src/generation/remote-client.ts +100 -77
  106. package/src/index.ts +16 -16
  107. package/src/output/file-writer.ts +123 -123
  108. package/src/output/formatter.ts +139 -132
  109. package/src/server.ts +12 -11
  110. package/src/stack/detect.ts +226 -175
  111. package/src/tokens/sync.ts +189 -155
  112. package/src/tools/analyze-screenshot-ui.ts +89 -88
  113. package/src/tools/analyze-tokens.ts +80 -78
  114. package/src/tools/catalog-components.ts +68 -68
  115. package/src/tools/create-ui.ts +295 -142
  116. package/src/tools/detect-ui-stack.ts +36 -36
  117. package/src/tools/generate-component-variants.ts +155 -135
  118. package/src/tools/generate-vibes.ts +121 -117
  119. package/src/tools/index.ts +14 -14
  120. package/src/tools/modify-ui.ts +170 -165
  121. package/src/tools/scaffold-project.ts +68 -66
  122. package/src/tools/snippet-ui.ts +323 -172
  123. package/src/tools/sync-design-tokens.ts +217 -195
  124. package/src/utils/walk.ts +47 -45
  125. package/src/version.ts +6 -0
  126. package/tsconfig.json +23 -33
  127. package/vitest.config.ts +10 -10
  128. package/.prettierrc +0 -9
  129. package/eslint.config.js +0 -37
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,GAAG,GAAG,OAAO,CAAC,iBAAiB,CAAyB,CAAC;AAE/D,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC"}
package/package.json CHANGED
@@ -1,56 +1,56 @@
1
1
  {
2
- "name": "@gemini-designer/mcp-server",
3
- "version": "0.1.2",
4
- "description": "MCP server for AI-powered UI design and code generation with Gemini",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "bin": {
8
- "gemini-designer-mcp": "dist/index.js"
9
- },
10
- "scripts": {
11
- "dev": "tsx watch src/index.ts",
12
- "build": "tsc",
13
- "start": "node dist/index.js",
14
- "test": "vitest",
15
- "test:coverage": "vitest --coverage",
16
- "lint": "eslint src/",
17
- "lint:fix": "eslint src/ --fix",
18
- "format": "prettier --write src/",
19
- "typecheck": "tsc --noEmit"
20
- },
21
- "keywords": [
22
- "mcp",
23
- "model-context-protocol",
24
- "gemini",
25
- "design-to-code",
26
- "ai",
27
- "frontend",
28
- "ui-generation"
29
- ],
30
- "author": "",
31
- "license": "MIT",
32
- "engines": {
33
- "node": ">=20.0.0"
34
- },
35
- "repository": {
36
- "type": "git",
37
- "url": "https://github.com/yourusername/gemini-designer-mcp"
38
- },
39
- "homepage": "https://gemini-designer.dev",
40
- "dependencies": {
41
- "@google/generative-ai": "^0.21.0",
42
- "@modelcontextprotocol/sdk": "^1.12.0",
43
- "prettier": "^3.4.0",
44
- "typescript": "^5.7.0",
45
- "zod": "^3.24.0"
46
- },
47
- "devDependencies": {
48
- "@types/node": "^22.10.0",
49
- "@typescript-eslint/eslint-plugin": "^8.18.0",
50
- "@typescript-eslint/parser": "^8.18.0",
51
- "@vitest/coverage-v8": "^2.1.0",
52
- "eslint": "^9.16.0",
53
- "tsx": "^4.19.0",
54
- "vitest": "^2.1.0"
55
- }
56
- }
2
+ "name": "@gemini-designer/mcp-server",
3
+ "version": "0.1.29",
4
+ "description": "MCP server for AI-powered UI design and code generation with Gemini",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "gemini-designer-mcp": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "dev": "tsx watch src/index.ts",
12
+ "build": "tsc",
13
+ "start": "node dist/index.js",
14
+ "test": "vitest run",
15
+ "test:coverage": "vitest run --coverage",
16
+ "lint": "eslint src/",
17
+ "lint:fix": "eslint src/ --fix",
18
+ "format": "prettier --write src/",
19
+ "typecheck": "tsc --noEmit"
20
+ },
21
+ "keywords": [
22
+ "mcp",
23
+ "model-context-protocol",
24
+ "gemini",
25
+ "design-to-code",
26
+ "ai",
27
+ "frontend",
28
+ "ui-generation"
29
+ ],
30
+ "author": "",
31
+ "license": "MIT",
32
+ "engines": {
33
+ "node": ">=20.0.0"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/yourusername/gemini-designer-mcp"
38
+ },
39
+ "homepage": "https://gemini-designer.dev",
40
+ "dependencies": {
41
+ "@google/generative-ai": "^0.21.0",
42
+ "@modelcontextprotocol/sdk": "^1.12.0",
43
+ "prettier": "^3.4.0",
44
+ "typescript": "^5.7.0",
45
+ "zod": "^3.24.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^22.10.0",
49
+ "@typescript-eslint/eslint-plugin": "^8.18.0",
50
+ "@typescript-eslint/parser": "^8.18.0",
51
+ "@vitest/coverage-v8": "^2.1.0",
52
+ "eslint": "^9.16.0",
53
+ "tsx": "^4.19.0",
54
+ "vitest": "^2.1.0"
55
+ }
56
+ }
@@ -6,26 +6,26 @@ import { describe, it, expect } from 'vitest';
6
6
  import { estimateTokens } from '../context/builder.js';
7
7
 
8
8
  describe('estimateTokens', () => {
9
- it('should estimate tokens based on character count', () => {
10
- // ~4 chars per token
11
- expect(estimateTokens('hello')).toBe(2); // 5 chars → ~2 tokens
12
- expect(estimateTokens('')).toBe(0);
13
- });
9
+ it('should estimate tokens based on character count', () => {
10
+ // ~4 chars per token
11
+ expect(estimateTokens('hello')).toBe(2); // 5 chars → ~2 tokens
12
+ expect(estimateTokens('')).toBe(0);
13
+ });
14
14
 
15
- it('should handle longer text', () => {
16
- const text = 'a'.repeat(100);
17
- expect(estimateTokens(text)).toBe(25); // 100 chars → 25 tokens
18
- });
15
+ it('should handle longer text', () => {
16
+ const text = 'a'.repeat(100);
17
+ expect(estimateTokens(text)).toBe(25); // 100 chars → 25 tokens
18
+ });
19
19
 
20
- it('should round up for partial tokens', () => {
21
- const text = 'ab'; // 2 chars → 0.5 → rounds to 1
22
- expect(estimateTokens(text)).toBe(1);
23
- });
20
+ it('should round up for partial tokens', () => {
21
+ const text = 'ab'; // 2 chars → 0.5 → rounds to 1
22
+ expect(estimateTokens(text)).toBe(1);
23
+ });
24
24
 
25
- it('should handle code snippets', () => {
26
- const code = 'const Button = () => <button>Click</button>;';
27
- const tokens = estimateTokens(code);
28
- expect(tokens).toBeGreaterThan(0);
29
- expect(tokens).toBe(Math.ceil(code.length / 4));
30
- });
25
+ it('should handle code snippets', () => {
26
+ const code = 'const Button = () => <button>Click</button>;';
27
+ const tokens = estimateTokens(code);
28
+ expect(tokens).toBeGreaterThan(0);
29
+ expect(tokens).toBe(Math.ceil(code.length / 4));
30
+ });
31
31
  });
@@ -5,48 +5,80 @@
5
5
  import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
6
6
 
7
7
  describe('Config', () => {
8
- const originalEnv = process.env;
8
+ const originalEnv = process.env;
9
9
 
10
- beforeEach(() => {
11
- vi.resetModules();
12
- process.env = { ...originalEnv };
10
+ beforeEach(() => {
11
+ vi.resetModules();
12
+ process.env = { ...originalEnv };
13
+ });
14
+
15
+ afterEach(() => {
16
+ process.env = originalEnv;
17
+ });
18
+
19
+ describe('loadConfigFromEnv', () => {
20
+ it('loads GEMINI_API_KEY and falls back to gemini provider when LiteLLM is not configured', async () => {
21
+ process.env.GEMINI_API_KEY = 'test-key';
22
+
23
+ // Dynamic import to get fresh module
24
+ const { loadConfig } = await import('../config/index.js');
25
+
26
+ const cfg = loadConfig();
27
+ expect(cfg.mode).toBe('local');
28
+ expect(cfg.apiKey).toBe('test-key');
29
+ expect(cfg.localProvider).toBe('gemini');
13
30
  });
14
31
 
15
- afterEach(() => {
16
- process.env = originalEnv;
32
+ it('falls back to litellm provider when GEMINI_API_KEY is missing but LITELLM_ENDPOINT is configured', async () => {
33
+ process.env.LITELLM_ENDPOINT = 'http://localhost:4000';
34
+
35
+ const { loadConfig } = await import('../config/index.js');
36
+ const cfg = loadConfig();
37
+ expect(cfg.mode).toBe('local');
38
+ expect(cfg.localProvider).toBe('litellm');
17
39
  });
18
40
 
19
- describe('loadConfigFromEnv', () => {
20
- it('should load API key from environment', async () => {
21
- process.env.GEMINI_API_KEY = 'test-key';
41
+ it('throws when remote mode is selected without a remote endpoint', async () => {
42
+ process.env.GEMINI_DESIGNER_MODE = 'remote';
43
+ process.env.GEMINI_DESIGNER_REMOTE_API_KEY = 'gd_test_key';
44
+
45
+ const { loadConfig } = await import('../config/index.js');
46
+ const spy = vi.spyOn(console, 'error').mockImplementation(() => {});
47
+ expect(() => loadConfig()).toThrow(/remote endpoint/i);
48
+ spy.mockRestore();
49
+ });
22
50
 
23
- // Dynamic import to get fresh module
24
- const { loadConfig } = await import('../config/index.js');
51
+ it('throws when remote endpoint is plain HTTP (non-localhost)', async () => {
52
+ process.env.GEMINI_DESIGNER_MODE = 'remote';
53
+ process.env.GEMINI_DESIGNER_REMOTE_API_KEY = 'gd_test_key';
54
+ process.env.GEMINI_DESIGNER_REMOTE_ENDPOINT = 'http://example.com';
25
55
 
26
- // This would throw without the key, so we test it exists
27
- expect(process.env.GEMINI_API_KEY).toBe('test-key');
28
- });
56
+ const { loadConfig } = await import('../config/index.js');
57
+ const spy = vi.spyOn(console, 'error').mockImplementation(() => {});
58
+ expect(() => loadConfig()).toThrow(/insecure remote endpoint/i);
59
+ spy.mockRestore();
60
+ });
29
61
 
30
- it('should respect mode environment variable', () => {
31
- process.env.GEMINI_DESIGNER_MODE = 'remote';
32
- expect(process.env.GEMINI_DESIGNER_MODE).toBe('remote');
33
- });
62
+ it('allows remote endpoint over HTTP for localhost (dev)', async () => {
63
+ process.env.GEMINI_DESIGNER_MODE = 'remote';
64
+ process.env.GEMINI_DESIGNER_REMOTE_API_KEY = 'gd_test_key';
65
+ process.env.GEMINI_DESIGNER_REMOTE_ENDPOINT = 'http://localhost:3000';
34
66
 
35
- it('should respect debug environment variable', () => {
36
- process.env.GEMINI_DESIGNER_DEBUG = 'true';
37
- expect(process.env.GEMINI_DESIGNER_DEBUG).toBe('true');
38
- });
67
+ const { loadConfig } = await import('../config/index.js');
68
+ const cfg = loadConfig();
69
+ expect(cfg.mode).toBe('remote');
70
+ expect(cfg.remoteEndpoint).toBe('http://localhost:3000');
39
71
  });
72
+ });
40
73
 
41
- describe('Config schema', () => {
42
- it('should have correct default framework', () => {
43
- // The default is 'react' in the schema
44
- expect(true).toBe(true); // Placeholder for schema validation
45
- });
74
+ describe('Config defaults', () => {
75
+ it('defaults framework to react and accessibility to wcag-aa', async () => {
76
+ process.env.GEMINI_API_KEY = 'test-key';
46
77
 
47
- it('should have correct default accessibility', () => {
48
- // The default is 'wcag-aa' in the schema
49
- expect(true).toBe(true); // Placeholder for schema validation
50
- });
78
+ const { loadConfig } = await import('../config/index.js');
79
+ const cfg = loadConfig();
80
+ expect(cfg.defaultFramework).toBe('react');
81
+ expect(cfg.accessibility).toBe('wcag-aa');
51
82
  });
83
+ });
52
84
  });
@@ -3,107 +3,113 @@
3
3
  */
4
4
 
5
5
  import { describe, it, expect } from 'vitest';
6
- import { isSensitiveFile, isPathAllowed, isUIRelevant, sanitizeContent } from '../context/filter.js';
6
+ import {
7
+ isSensitiveFile,
8
+ isPathAllowed,
9
+ isUIRelevant,
10
+ sanitizeContent,
11
+ } from '../context/filter.js';
7
12
 
8
13
  describe('isSensitiveFile', () => {
9
- it('should flag .env files as sensitive', () => {
10
- expect(isSensitiveFile('.env')).toBe(true);
11
- expect(isSensitiveFile('.env.local')).toBe(true);
12
- expect(isSensitiveFile('/path/to/.env.production')).toBe(true);
13
- });
14
-
15
- it('should flag secret files as sensitive', () => {
16
- expect(isSensitiveFile('secrets.json')).toBe(true);
17
- expect(isSensitiveFile('/config/secrets.yaml')).toBe(true);
18
- });
19
-
20
- it('should flag private keys as sensitive', () => {
21
- expect(isSensitiveFile('server.key')).toBe(true);
22
- expect(isSensitiveFile('certificate.pem')).toBe(true);
23
- expect(isSensitiveFile('id_rsa')).toBe(true);
24
- });
25
-
26
- it('should flag backend directories as sensitive', () => {
27
- expect(isSensitiveFile('/src/api/routes.ts')).toBe(true);
28
- expect(isSensitiveFile('/server/index.ts')).toBe(true);
29
- expect(isSensitiveFile('/backend/handlers.ts')).toBe(true);
30
- });
31
-
32
- it('should not flag UI files as sensitive', () => {
33
- expect(isSensitiveFile('Button.tsx')).toBe(false);
34
- expect(isSensitiveFile('/components/Card.tsx')).toBe(false);
35
- expect(isSensitiveFile('styles.css')).toBe(false);
36
- });
14
+ it('should flag .env files as sensitive', () => {
15
+ expect(isSensitiveFile('.env')).toBe(true);
16
+ expect(isSensitiveFile('.env.local')).toBe(true);
17
+ expect(isSensitiveFile('/path/to/.env.production')).toBe(true);
18
+ });
19
+
20
+ it('should flag secret files as sensitive', () => {
21
+ expect(isSensitiveFile('secrets.json')).toBe(true);
22
+ expect(isSensitiveFile('/config/secrets.yaml')).toBe(true);
23
+ });
24
+
25
+ it('should flag private keys as sensitive', () => {
26
+ expect(isSensitiveFile('server.key')).toBe(true);
27
+ expect(isSensitiveFile('certificate.pem')).toBe(true);
28
+ expect(isSensitiveFile('id_rsa')).toBe(true);
29
+ });
30
+
31
+ it('should flag backend directories as sensitive', () => {
32
+ expect(isSensitiveFile('/src/api/routes.ts')).toBe(true);
33
+ expect(isSensitiveFile('/server/index.ts')).toBe(true);
34
+ expect(isSensitiveFile('/backend/handlers.ts')).toBe(true);
35
+ });
36
+
37
+ it('should not flag UI files as sensitive', () => {
38
+ expect(isSensitiveFile('Button.tsx')).toBe(false);
39
+ expect(isSensitiveFile('/components/Card.tsx')).toBe(false);
40
+ expect(isSensitiveFile('styles.css')).toBe(false);
41
+ });
37
42
  });
38
43
 
39
44
  describe('isPathAllowed', () => {
40
- it('should allow paths within allowed directories', () => {
41
- expect(isPathAllowed('/project/src/file.ts', ['/project'])).toBe(true);
42
- expect(isPathAllowed('/project/components/Button.tsx', ['/project'])).toBe(true);
43
- });
44
-
45
- it('should reject paths outside allowed directories', () => {
46
- expect(isPathAllowed('/etc/passwd', ['/project'])).toBe(false);
47
- expect(isPathAllowed('/home/user/secrets', ['/project'])).toBe(false);
48
- });
49
-
50
- it('should support multiple allowed paths', () => {
51
- const allowed = ['/project', '/shared'];
52
- expect(isPathAllowed('/project/file.ts', allowed)).toBe(true);
53
- expect(isPathAllowed('/shared/utils.ts', allowed)).toBe(true);
54
- expect(isPathAllowed('/other/file.ts', allowed)).toBe(false);
55
- });
45
+ it('should allow paths within allowed directories', () => {
46
+ expect(isPathAllowed('/project/src/file.ts', ['/project'])).toBe(true);
47
+ expect(isPathAllowed('/project/components/Button.tsx', ['/project'])).toBe(true);
48
+ });
49
+
50
+ it('should reject paths outside allowed directories', () => {
51
+ expect(isPathAllowed('/etc/passwd', ['/project'])).toBe(false);
52
+ expect(isPathAllowed('/home/user/secrets', ['/project'])).toBe(false);
53
+ });
54
+
55
+ it('should support multiple allowed paths', () => {
56
+ const allowed = ['/project', '/shared'];
57
+ expect(isPathAllowed('/project/file.ts', allowed)).toBe(true);
58
+ expect(isPathAllowed('/shared/utils.ts', allowed)).toBe(true);
59
+ expect(isPathAllowed('/other/file.ts', allowed)).toBe(false);
60
+ });
56
61
  });
57
62
 
58
63
  describe('isUIRelevant', () => {
59
- it('should identify CSS files as UI relevant', () => {
60
- expect(isUIRelevant('styles.css')).toBe(true);
61
- expect(isUIRelevant('theme.scss')).toBe(true);
62
- expect(isUIRelevant('variables.less')).toBe(true);
63
- });
64
-
65
- it('should identify component files as UI relevant', () => {
66
- expect(isUIRelevant('Button.tsx')).toBe(true);
67
- expect(isUIRelevant('Card.jsx')).toBe(true);
68
- expect(isUIRelevant('Header.vue')).toBe(true);
69
- expect(isUIRelevant('Nav.svelte')).toBe(true);
70
- });
71
-
72
- it('should identify design token files as UI relevant', () => {
73
- expect(isUIRelevant('tokens.json')).toBe(true);
74
- expect(isUIRelevant('design-tokens.ts')).toBe(true);
75
- expect(isUIRelevant('theme.ts')).toBe(true);
76
- });
77
-
78
- it('should not identify backend files as UI relevant', () => {
79
- expect(isUIRelevant('database.ts')).toBe(false);
80
- expect(isUIRelevant('server.ts')).toBe(false);
81
- });
64
+ it('should identify CSS files as UI relevant', () => {
65
+ expect(isUIRelevant('styles.css')).toBe(true);
66
+ expect(isUIRelevant('theme.scss')).toBe(true);
67
+ expect(isUIRelevant('variables.less')).toBe(true);
68
+ });
69
+
70
+ it('should identify component files as UI relevant', () => {
71
+ expect(isUIRelevant('Button.tsx')).toBe(true);
72
+ expect(isUIRelevant('Card.jsx')).toBe(true);
73
+ expect(isUIRelevant('Header.vue')).toBe(true);
74
+ expect(isUIRelevant('Nav.svelte')).toBe(true);
75
+ });
76
+
77
+ it('should identify design token files as UI relevant', () => {
78
+ expect(isUIRelevant('tokens.json')).toBe(true);
79
+ expect(isUIRelevant('design-tokens.ts')).toBe(true);
80
+ expect(isUIRelevant('theme.ts')).toBe(true);
81
+ });
82
+
83
+ it('should not identify backend files as UI relevant', () => {
84
+ expect(isUIRelevant('database.ts')).toBe(false);
85
+ expect(isUIRelevant('server.ts')).toBe(false);
86
+ });
82
87
  });
83
88
 
84
89
  describe('sanitizeContent', () => {
85
- it('should redact API key patterns', () => {
86
- const content = 'const apiKey = "sk-abc123xyz";';
87
- expect(sanitizeContent(content)).toContain('[REDACTED]');
88
- });
89
-
90
- it('should redact Bearer tokens', () => {
91
- const content = 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ';
92
- expect(sanitizeContent(content)).toContain('[REDACTED]');
93
- });
94
-
95
- it('should redact AWS keys', () => {
96
- const content = 'aws_access_key = "AKIAIOSFODNN7EXAMPLE"';
97
- expect(sanitizeContent(content)).toContain('[REDACTED]');
98
- });
99
-
100
- it('should redact database connection strings', () => {
101
- const content = 'DATABASE_URL=postgresql://user:password@localhost:5432/db';
102
- expect(sanitizeContent(content)).toContain('[REDACTED]');
103
- });
104
-
105
- it('should preserve non-sensitive content', () => {
106
- const content = 'const Button = () => <button>Click me</button>;';
107
- expect(sanitizeContent(content)).toBe(content);
108
- });
90
+ it('should redact API key patterns', () => {
91
+ const content = 'const apiKey = "sk-abc123xyz";';
92
+ expect(sanitizeContent(content)).toContain('[REDACTED]');
93
+ });
94
+
95
+ it('should redact Bearer tokens', () => {
96
+ const content =
97
+ 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ';
98
+ expect(sanitizeContent(content)).toContain('[REDACTED]');
99
+ });
100
+
101
+ it('should redact AWS keys', () => {
102
+ const content = 'aws_access_key = "AKIAIOSFODNN7EXAMPLE"';
103
+ expect(sanitizeContent(content)).toContain('[REDACTED]');
104
+ });
105
+
106
+ it('should redact database connection strings', () => {
107
+ const content = 'DATABASE_URL=postgresql://user:password@localhost:5432/db';
108
+ expect(sanitizeContent(content)).toContain('[REDACTED]');
109
+ });
110
+
111
+ it('should preserve non-sensitive content', () => {
112
+ const content = 'const Button = () => <button>Click me</button>;';
113
+ expect(sanitizeContent(content)).toBe(content);
114
+ });
109
115
  });