@gemini-designer/mcp-server 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/catalog.d.ts.map +1 -1
- package/dist/components/catalog.js +10 -4
- package/dist/components/catalog.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +2 -6
- package/dist/config/index.js.map +1 -1
- package/dist/context/builder.d.ts.map +1 -1
- package/dist/context/builder.js.map +1 -1
- package/dist/context/filter.js.map +1 -1
- package/dist/context/grounding.d.ts.map +1 -1
- package/dist/context/grounding.js +7 -3
- package/dist/context/grounding.js.map +1 -1
- package/dist/context/guards.js.map +1 -1
- package/dist/context/repo-hints.js.map +1 -1
- package/dist/context/styling-detector.d.ts +24 -0
- package/dist/context/styling-detector.d.ts.map +1 -0
- package/dist/context/styling-detector.js +337 -0
- package/dist/context/styling-detector.js.map +1 -0
- package/dist/design/principles.js.map +1 -1
- package/dist/generation/gemini-client.d.ts.map +1 -1
- package/dist/generation/gemini-client.js.map +1 -1
- package/dist/generation/litellm-client.d.ts.map +1 -1
- package/dist/generation/litellm-client.js +14 -7
- package/dist/generation/litellm-client.js.map +1 -1
- package/dist/generation/remote-client.d.ts.map +1 -1
- package/dist/generation/remote-client.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/output/file-writer.d.ts.map +1 -1
- package/dist/output/file-writer.js.map +1 -1
- package/dist/output/formatter.d.ts.map +1 -1
- package/dist/output/formatter.js +5 -2
- package/dist/output/formatter.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -1
- package/dist/server.js.map +1 -1
- package/dist/stack/detect.d.ts.map +1 -1
- package/dist/stack/detect.js +42 -9
- package/dist/stack/detect.js.map +1 -1
- package/dist/tokens/sync.d.ts.map +1 -1
- package/dist/tokens/sync.js +22 -5
- package/dist/tokens/sync.js.map +1 -1
- package/dist/tools/analyze-screenshot-ui.d.ts.map +1 -1
- package/dist/tools/analyze-screenshot-ui.js +5 -5
- package/dist/tools/analyze-screenshot-ui.js.map +1 -1
- package/dist/tools/analyze-tokens.d.ts.map +1 -1
- package/dist/tools/analyze-tokens.js +3 -1
- package/dist/tools/analyze-tokens.js.map +1 -1
- package/dist/tools/catalog-components.d.ts.map +1 -1
- package/dist/tools/catalog-components.js +1 -4
- package/dist/tools/catalog-components.js.map +1 -1
- package/dist/tools/create-ui.d.ts +3 -0
- package/dist/tools/create-ui.d.ts.map +1 -1
- package/dist/tools/create-ui.js +203 -75
- package/dist/tools/create-ui.js.map +1 -1
- package/dist/tools/detect-ui-stack.js.map +1 -1
- package/dist/tools/generate-component-variants.d.ts.map +1 -1
- package/dist/tools/generate-component-variants.js +15 -4
- package/dist/tools/generate-component-variants.js.map +1 -1
- package/dist/tools/generate-vibes.d.ts.map +1 -1
- package/dist/tools/generate-vibes.js +7 -3
- package/dist/tools/generate-vibes.js.map +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/modify-ui.d.ts.map +1 -1
- package/dist/tools/modify-ui.js +7 -2
- package/dist/tools/modify-ui.js.map +1 -1
- package/dist/tools/scaffold-project.d.ts.map +1 -1
- package/dist/tools/scaffold-project.js +3 -1
- package/dist/tools/scaffold-project.js.map +1 -1
- package/dist/tools/snippet-ui.d.ts +3 -1
- package/dist/tools/snippet-ui.d.ts.map +1 -1
- package/dist/tools/snippet-ui.js +219 -88
- package/dist/tools/snippet-ui.js.map +1 -1
- package/dist/tools/sync-design-tokens.d.ts.map +1 -1
- package/dist/tools/sync-design-tokens.js +26 -11
- package/dist/tools/sync-design-tokens.js.map +1 -1
- package/dist/utils/walk.d.ts.map +1 -1
- package/dist/utils/walk.js.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +54 -54
- package/src/__tests__/builder.test.ts +19 -19
- package/src/__tests__/config.test.ts +43 -33
- package/src/__tests__/filter.test.ts +98 -92
- package/src/__tests__/remote-client.test.ts +164 -0
- package/src/components/catalog.ts +170 -166
- package/src/config/index.ts +174 -178
- package/src/context/builder.ts +157 -157
- package/src/context/filter.ts +103 -103
- package/src/context/grounding.ts +143 -129
- package/src/context/guards.ts +39 -39
- package/src/context/repo-hints.ts +24 -24
- package/src/context/styling-detector.ts +460 -0
- package/src/design/principles.ts +14 -14
- package/src/generation/gemini-client.ts +53 -56
- package/src/generation/litellm-client.ts +102 -86
- package/src/generation/remote-client.ts +73 -72
- package/src/index.ts +16 -16
- package/src/output/file-writer.ts +122 -122
- package/src/output/formatter.ts +139 -132
- package/src/server.ts +12 -11
- package/src/stack/detect.ts +226 -175
- package/src/tokens/sync.ts +189 -155
- package/src/tools/analyze-screenshot-ui.ts +89 -88
- package/src/tools/analyze-tokens.ts +80 -78
- package/src/tools/catalog-components.ts +68 -68
- package/src/tools/create-ui.ts +295 -142
- package/src/tools/detect-ui-stack.ts +36 -36
- package/src/tools/generate-component-variants.ts +155 -135
- package/src/tools/generate-vibes.ts +121 -117
- package/src/tools/index.ts +14 -14
- package/src/tools/modify-ui.ts +170 -165
- package/src/tools/scaffold-project.ts +68 -66
- package/src/tools/snippet-ui.ts +323 -172
- package/src/tools/sync-design-tokens.ts +217 -195
- package/src/utils/walk.ts +47 -45
- package/src/version.ts +6 -0
- package/tsconfig.json +23 -33
- package/vitest.config.ts +10 -10
- package/.prettierrc +0 -9
- package/eslint.config.js +0 -37
package/src/context/builder.ts
CHANGED
|
@@ -17,217 +17,217 @@ const MAX_CONTEXT_TOKENS = 12500; // ~50k chars
|
|
|
17
17
|
const MAX_FILE_TOKENS = 2500; // ~10k chars per file
|
|
18
18
|
|
|
19
19
|
export interface ContextResult {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
content: string;
|
|
21
|
+
estimatedTokens: number;
|
|
22
|
+
filesIncluded: string[];
|
|
23
|
+
filesSkipped: string[];
|
|
24
|
+
truncated: boolean;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Estimate token count from text
|
|
29
29
|
*/
|
|
30
30
|
export function estimateTokens(text: string): number {
|
|
31
|
-
|
|
31
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Build context from specified file paths with token optimization
|
|
36
36
|
*/
|
|
37
37
|
export async function buildContext(paths: string[], config: Config): Promise<string> {
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
const result = await buildContextWithMetadata(paths, config);
|
|
39
|
+
return result.content;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Build context with full metadata (tokens, files included, etc.)
|
|
44
44
|
*/
|
|
45
45
|
export async function buildContextWithMetadata(
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
paths: string[],
|
|
47
|
+
config: Config
|
|
48
48
|
): Promise<ContextResult> {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// Security check: not a sensitive file
|
|
72
|
-
if (isSensitiveFile(absPath)) {
|
|
73
|
-
if (config.debug) {
|
|
74
|
-
console.error(`[context] Skipping ${filePath}: sensitive file`);
|
|
75
|
-
}
|
|
76
|
-
filesSkipped.push(`${filePath} (sensitive)`);
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
49
|
+
const contents: string[] = [];
|
|
50
|
+
const filesIncluded: string[] = [];
|
|
51
|
+
const filesSkipped: string[] = [];
|
|
52
|
+
let totalTokens = 0;
|
|
53
|
+
let truncated = false;
|
|
54
|
+
|
|
55
|
+
// Sort paths by likely relevance (design tokens first, then components)
|
|
56
|
+
const sortedPaths = sortByRelevance(paths);
|
|
57
|
+
|
|
58
|
+
for (const filePath of sortedPaths) {
|
|
59
|
+
// Resolve to absolute path
|
|
60
|
+
const absPath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
|
|
61
|
+
|
|
62
|
+
// Security check: path must be in allowed paths
|
|
63
|
+
if (!isPathAllowed(absPath, config.allowedPaths)) {
|
|
64
|
+
if (config.debug) {
|
|
65
|
+
console.error(`[context] Skipping ${filePath}: outside allowed paths`);
|
|
66
|
+
}
|
|
67
|
+
filesSkipped.push(`${filePath} (outside allowed paths)`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
79
70
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
71
|
+
// Security check: not a sensitive file
|
|
72
|
+
if (isSensitiveFile(absPath)) {
|
|
73
|
+
if (config.debug) {
|
|
74
|
+
console.error(`[context] Skipping ${filePath}: sensitive file`);
|
|
75
|
+
}
|
|
76
|
+
filesSkipped.push(`${filePath} (sensitive)`);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
85
79
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
80
|
+
// Check if file exists and is a file
|
|
81
|
+
if (!fs.existsSync(absPath)) {
|
|
82
|
+
filesSkipped.push(`${filePath} (not found)`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
91
85
|
|
|
92
|
-
|
|
93
|
-
|
|
86
|
+
const stat = fs.statSync(absPath);
|
|
87
|
+
if (!stat.isFile()) {
|
|
88
|
+
filesSkipped.push(`${filePath} (not a file)`);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
94
91
|
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
try {
|
|
93
|
+
let content = fs.readFileSync(absPath, 'utf-8');
|
|
97
94
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const maxFileChars = MAX_FILE_TOKENS * CHARS_PER_TOKEN;
|
|
95
|
+
// Sanitize content to remove any secrets
|
|
96
|
+
content = sanitizeContent(content);
|
|
101
97
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
fileTokens = MAX_FILE_TOKENS;
|
|
106
|
-
}
|
|
98
|
+
// Calculate tokens for this file
|
|
99
|
+
let fileTokens = estimateTokens(content);
|
|
100
|
+
const maxFileChars = MAX_FILE_TOKENS * CHARS_PER_TOKEN;
|
|
107
101
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
truncated = true;
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
102
|
+
// Truncate large files
|
|
103
|
+
if (fileTokens > MAX_FILE_TOKENS) {
|
|
104
|
+
content = smartTruncate(content, maxFileChars);
|
|
105
|
+
fileTokens = MAX_FILE_TOKENS;
|
|
106
|
+
}
|
|
116
107
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
totalTokens += fileTokens;
|
|
122
|
-
} catch (error) {
|
|
123
|
-
if (config.debug) {
|
|
124
|
-
console.error(`[context] Error reading ${filePath}:`, error);
|
|
125
|
-
}
|
|
126
|
-
filesSkipped.push(`${filePath} (read error)`);
|
|
108
|
+
// Check if adding this would exceed total limit
|
|
109
|
+
if (totalTokens + fileTokens > MAX_CONTEXT_TOKENS) {
|
|
110
|
+
if (config.debug) {
|
|
111
|
+
console.error(`[context] Stopping: token limit reached`);
|
|
127
112
|
}
|
|
113
|
+
truncated = true;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const ext = path.extname(absPath);
|
|
118
|
+
const header = `/* File: ${path.basename(absPath)} (${ext}) - ~${fileTokens} tokens */`;
|
|
119
|
+
contents.push(`${header}\n${content}`);
|
|
120
|
+
filesIncluded.push(filePath);
|
|
121
|
+
totalTokens += fileTokens;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (config.debug) {
|
|
124
|
+
console.error(`[context] Error reading ${filePath}:`, error);
|
|
125
|
+
}
|
|
126
|
+
filesSkipped.push(`${filePath} (read error)`);
|
|
128
127
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
content: contents.length > 0 ? contents.join('\n\n---\n\n') : '',
|
|
132
|
+
estimatedTokens: totalTokens,
|
|
133
|
+
filesIncluded,
|
|
134
|
+
filesSkipped,
|
|
135
|
+
truncated,
|
|
136
|
+
};
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
/**
|
|
140
140
|
* Sort paths by relevance (design tokens and variables first)
|
|
141
141
|
*/
|
|
142
142
|
function sortByRelevance(paths: string[]): string[] {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
143
|
+
const priority: Record<string, number> = {
|
|
144
|
+
tokens: 0,
|
|
145
|
+
variables: 0,
|
|
146
|
+
theme: 1,
|
|
147
|
+
design: 1,
|
|
148
|
+
colors: 2,
|
|
149
|
+
typography: 2,
|
|
150
|
+
styles: 3,
|
|
151
|
+
css: 4,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
return [...paths].sort((a, b) => {
|
|
155
|
+
const aName = path.basename(a).toLowerCase();
|
|
156
|
+
const bName = path.basename(b).toLowerCase();
|
|
157
|
+
|
|
158
|
+
let aPriority = 10;
|
|
159
|
+
let bPriority = 10;
|
|
160
|
+
|
|
161
|
+
for (const [key, value] of Object.entries(priority)) {
|
|
162
|
+
if (aName.includes(key)) aPriority = Math.min(aPriority, value);
|
|
163
|
+
if (bName.includes(key)) bPriority = Math.min(bPriority, value);
|
|
164
|
+
}
|
|
165
165
|
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
return aPriority - bPriority;
|
|
167
|
+
});
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
/**
|
|
171
171
|
* Smart truncate: keep beginning and end, with clear indicator
|
|
172
172
|
*/
|
|
173
173
|
function smartTruncate(content: string, maxChars: number): string {
|
|
174
|
-
|
|
174
|
+
if (content.length <= maxChars) return content;
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
const keepStart = Math.floor(maxChars * 0.7);
|
|
177
|
+
const keepEnd = Math.floor(maxChars * 0.2);
|
|
178
178
|
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
const start = content.slice(0, keepStart);
|
|
180
|
+
const end = content.slice(-keepEnd);
|
|
181
181
|
|
|
182
|
-
|
|
182
|
+
return `${start}\n\n/* ... [${Math.round((content.length - maxChars) / 1000)}k chars truncated] ... */\n\n${end}`;
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
/**
|
|
186
186
|
* Automatically discover relevant UI files in a directory
|
|
187
187
|
*/
|
|
188
188
|
export async function discoverUIFiles(directory: string, config: Config): Promise<string[]> {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
189
|
+
const uiPatterns = [
|
|
190
|
+
/\.(css|scss|less|sass)$/,
|
|
191
|
+
/\.(tsx|jsx)$/,
|
|
192
|
+
/\.(vue|svelte)$/,
|
|
193
|
+
/theme\./,
|
|
194
|
+
/design[-_]?tokens?\./,
|
|
195
|
+
/tailwind\.config\./,
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
const files: string[] = [];
|
|
199
|
+
|
|
200
|
+
function scan(dir: string, depth: number = 0) {
|
|
201
|
+
if (depth > 3) return; // Max depth
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
205
|
+
|
|
206
|
+
for (const entry of entries) {
|
|
207
|
+
const fullPath = path.join(dir, entry.name);
|
|
208
|
+
|
|
209
|
+
// Skip node_modules, .git, etc.
|
|
210
|
+
if (entry.isDirectory()) {
|
|
211
|
+
if (['node_modules', '.git', 'dist', 'build', '.next', '.nuxt'].includes(entry.name)) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
scan(fullPath, depth + 1);
|
|
215
|
+
} else if (entry.isFile()) {
|
|
216
|
+
// Check if matches UI patterns
|
|
217
|
+
if (uiPatterns.some((pattern) => pattern.test(entry.name))) {
|
|
218
|
+
if (isPathAllowed(fullPath, config.allowedPaths) && !isSensitiveFile(fullPath)) {
|
|
219
|
+
files.push(fullPath);
|
|
223
220
|
}
|
|
224
|
-
|
|
225
|
-
// Skip directories we can't read
|
|
221
|
+
}
|
|
226
222
|
}
|
|
223
|
+
}
|
|
224
|
+
} catch {
|
|
225
|
+
// Skip directories we can't read
|
|
227
226
|
}
|
|
227
|
+
}
|
|
228
228
|
|
|
229
|
-
|
|
229
|
+
scan(directory);
|
|
230
230
|
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
// Sort by relevance
|
|
232
|
+
return sortByRelevance(files);
|
|
233
233
|
}
|
package/src/context/filter.ts
CHANGED
|
@@ -11,124 +11,124 @@ import * as path from 'node:path';
|
|
|
11
11
|
* Files/directories that should NEVER be included in context
|
|
12
12
|
*/
|
|
13
13
|
const SENSITIVE_PATTERNS = [
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
14
|
+
// Environment and secrets
|
|
15
|
+
/\.env/i,
|
|
16
|
+
/secrets?\./i,
|
|
17
|
+
/\.pem$/i,
|
|
18
|
+
/\.key$/i,
|
|
19
|
+
/\.crt$/i,
|
|
20
|
+
/credentials/i,
|
|
21
|
+
/\.htpasswd/i,
|
|
22
|
+
|
|
23
|
+
// Private keys and certificates
|
|
24
|
+
/id_rsa/i,
|
|
25
|
+
/id_ed25519/i,
|
|
26
|
+
/\.p12$/i,
|
|
27
|
+
/\.pfx$/i,
|
|
28
|
+
|
|
29
|
+
// Config with potential secrets
|
|
30
|
+
/\.npmrc$/i,
|
|
31
|
+
/\.pypirc$/i,
|
|
32
|
+
/kubeconfig/i,
|
|
33
|
+
/\.docker\/config\.json$/i,
|
|
34
|
+
|
|
35
|
+
// Database
|
|
36
|
+
/\.sqlite$/i,
|
|
37
|
+
/\.db$/i,
|
|
38
|
+
/migrations?\//i,
|
|
39
|
+
/seeds?\//i,
|
|
40
|
+
|
|
41
|
+
// Backend/server code (when isolating UI)
|
|
42
|
+
/\/api\//i,
|
|
43
|
+
/\/server\//i,
|
|
44
|
+
/\/backend\//i,
|
|
45
|
+
/\/functions\//i, // Serverless
|
|
46
|
+
/\/lambda\//i,
|
|
47
|
+
/\/middleware\//i,
|
|
48
|
+
|
|
49
|
+
// Auth-related
|
|
50
|
+
/\/auth\//i,
|
|
51
|
+
/passport/i,
|
|
52
|
+
/jwt/i,
|
|
53
|
+
|
|
54
|
+
// System and dependencies
|
|
55
|
+
/node_modules\//i,
|
|
56
|
+
/\.git\//i,
|
|
57
|
+
/vendor\//i,
|
|
58
|
+
/\.cache\//i,
|
|
59
|
+
/\.next\//i,
|
|
60
|
+
/\.nuxt\//i,
|
|
61
|
+
/dist\//i,
|
|
62
|
+
/build\//i,
|
|
63
63
|
];
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* UI-relevant file patterns (for auto-discovery)
|
|
67
67
|
*/
|
|
68
68
|
export const UI_INCLUDE_PATTERNS = [
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
69
|
+
// Stylesheets
|
|
70
|
+
/\.(css|scss|less|sass|styl)$/i,
|
|
71
|
+
|
|
72
|
+
// Components
|
|
73
|
+
/\.(tsx|jsx)$/i,
|
|
74
|
+
/\.(vue|svelte)$/i,
|
|
75
|
+
|
|
76
|
+
// Design tokens
|
|
77
|
+
/theme\./i,
|
|
78
|
+
/tokens?\./i,
|
|
79
|
+
/variables\./i,
|
|
80
|
+
/design[-_]?system/i,
|
|
81
|
+
|
|
82
|
+
// Config files for styling
|
|
83
|
+
/tailwind\.config/i,
|
|
84
|
+
/postcss\.config/i,
|
|
85
|
+
/styled-components/i,
|
|
86
86
|
];
|
|
87
87
|
|
|
88
88
|
/**
|
|
89
89
|
* Check if a file path matches sensitive patterns
|
|
90
90
|
*/
|
|
91
91
|
export function isSensitiveFile(filePath: string): boolean {
|
|
92
|
-
|
|
92
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
94
|
+
for (const pattern of SENSITIVE_PATTERNS) {
|
|
95
|
+
if (pattern.test(normalized)) {
|
|
96
|
+
return true;
|
|
98
97
|
}
|
|
98
|
+
}
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
return false;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
/**
|
|
104
104
|
* Check if a file path is within allowed paths
|
|
105
105
|
*/
|
|
106
106
|
export function isPathAllowed(filePath: string, allowedPaths: string[]): boolean {
|
|
107
|
-
|
|
107
|
+
const absPath = path.resolve(filePath);
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
109
|
+
for (const allowed of allowedPaths) {
|
|
110
|
+
const absAllowed = path.resolve(allowed);
|
|
111
|
+
if (absPath.startsWith(absAllowed)) {
|
|
112
|
+
return true;
|
|
114
113
|
}
|
|
114
|
+
}
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
return false;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
/**
|
|
120
120
|
* Check if a file is UI-relevant
|
|
121
121
|
*/
|
|
122
122
|
export function isUIRelevant(filePath: string): boolean {
|
|
123
|
-
|
|
123
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
125
|
+
for (const pattern of UI_INCLUDE_PATTERNS) {
|
|
126
|
+
if (pattern.test(normalized)) {
|
|
127
|
+
return true;
|
|
129
128
|
}
|
|
129
|
+
}
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
return false;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/**
|
|
@@ -136,29 +136,29 @@ export function isUIRelevant(filePath: string): boolean {
|
|
|
136
136
|
* This is a best-effort filter for dynamic content
|
|
137
137
|
*/
|
|
138
138
|
export function sanitizeContent(content: string): string {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
// Remove common secret patterns
|
|
140
|
+
const patterns = [
|
|
141
|
+
// API keys (generic patterns)
|
|
142
|
+
/(['"`])?(api[_-]?key|apikey|secret|password|token|auth)(['"`])?[\s]*[:=][\s]*['"`][^'"`]+['"`]/gi,
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
// Bearer tokens
|
|
145
|
+
/Bearer\s+[A-Za-z0-9\-_=]+\.[A-Za-z0-9\-_=]+\.?[A-Za-z0-9\-_.+/=]*/gi,
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
// AWS keys
|
|
148
|
+
/AKIA[0-9A-Z]{16}/g,
|
|
149
149
|
|
|
150
|
-
|
|
151
|
-
|
|
150
|
+
// Private keys
|
|
151
|
+
/-----BEGIN[\s\w]+PRIVATE KEY-----[\s\S]+?-----END[\s\w]+PRIVATE KEY-----/g,
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
// Connection strings
|
|
154
|
+
/(mongodb|postgresql|mysql|redis):\/\/[^\s'"]+/gi,
|
|
155
|
+
];
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
let result = content;
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
for (const pattern of patterns) {
|
|
160
|
+
result = result.replace(pattern, '[REDACTED]');
|
|
161
|
+
}
|
|
162
162
|
|
|
163
|
-
|
|
163
|
+
return result;
|
|
164
164
|
}
|