uilint-core 0.1.30 → 0.1.32
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/{chunk-FMKY4JFW.js → chunk-FIESH4VM.js} +155 -14
- package/dist/chunk-FIESH4VM.js.map +1 -0
- package/dist/index.d.ts +90 -1
- package/dist/index.js +3 -1
- package/dist/node.d.ts +9 -2
- package/dist/node.js +14 -1
- package/dist/node.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-FMKY4JFW.js.map +0 -1
|
@@ -82,6 +82,68 @@ Focus on:
|
|
|
82
82
|
|
|
83
83
|
Be minimal. Only report significant inconsistencies.`;
|
|
84
84
|
}
|
|
85
|
+
function buildSourceScanPrompt(sourceCode, styleGuide, options = {}) {
|
|
86
|
+
const fileLabel = options.filePath || "component.tsx";
|
|
87
|
+
const dataLocList = Array.isArray(options.dataLocs) ? options.dataLocs : [];
|
|
88
|
+
const focusSection = options.componentName && options.componentName.trim() ? `## Focus
|
|
89
|
+
|
|
90
|
+
Focus on the "${options.componentName.trim()}" component${typeof options.componentLine === "number" ? ` (near line ${options.componentLine})` : ""}.
|
|
91
|
+
|
|
92
|
+
` : "";
|
|
93
|
+
const scopeText = options.includeChildren === true ? "Scope: selected element + children." : "Scope: selected element only.";
|
|
94
|
+
const dataLocSection = dataLocList.length > 0 ? `## Element Locations (data-loc values)
|
|
95
|
+
|
|
96
|
+
The following elements are rendered on the page from this file. Each data-loc has format "path:line:column".
|
|
97
|
+
When reporting issues, include the matching dataLoc value so we can highlight the specific element.
|
|
98
|
+
|
|
99
|
+
\`\`\`
|
|
100
|
+
${dataLocList.join("\n")}
|
|
101
|
+
\`\`\`
|
|
102
|
+
|
|
103
|
+
` : "";
|
|
104
|
+
const guideSection = styleGuide ? `## Style Guide
|
|
105
|
+
|
|
106
|
+
${styleGuide}
|
|
107
|
+
|
|
108
|
+
` : "";
|
|
109
|
+
return `You are a UI code reviewer. Analyze the following React/TypeScript UI code for style consistency issues.
|
|
110
|
+
|
|
111
|
+
${guideSection}${focusSection}${scopeText}
|
|
112
|
+
|
|
113
|
+
${dataLocSection}## Source Code (${fileLabel})
|
|
114
|
+
|
|
115
|
+
\`\`\`tsx
|
|
116
|
+
${sourceCode}
|
|
117
|
+
\`\`\`
|
|
118
|
+
|
|
119
|
+
## Task
|
|
120
|
+
|
|
121
|
+
Identify any style inconsistencies, violations of best practices, or deviations from the style guide.
|
|
122
|
+
For each issue, provide:
|
|
123
|
+
- line: the line number in the source file
|
|
124
|
+
- message: a clear description of the issue
|
|
125
|
+
- dataLoc: the matching data-loc value from the list above (if applicable, to identify the specific element)
|
|
126
|
+
|
|
127
|
+
## Critical Requirements for dataLoc
|
|
128
|
+
|
|
129
|
+
- If "Element Locations (data-loc values)" are provided, ONLY report issues that correspond to one of those elements.
|
|
130
|
+
- When you include a dataLoc, it MUST be copied verbatim from the list (no shortening paths, no normalization).
|
|
131
|
+
- If you cannot match an issue to a provided dataLoc, omit that issue from the response.
|
|
132
|
+
|
|
133
|
+
Respond with JSON:
|
|
134
|
+
\`\`\`json
|
|
135
|
+
{
|
|
136
|
+
"issues": [
|
|
137
|
+
{ "line": 12, "message": "Color #3575E2 should be #3B82F6 (primary blue from styleguide)", "dataLoc": "app/page.tsx:12:5" }
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
\`\`\`
|
|
141
|
+
|
|
142
|
+
If no issues are found, respond with:
|
|
143
|
+
\`\`\`json
|
|
144
|
+
{ "issues": [] }
|
|
145
|
+
\`\`\``;
|
|
146
|
+
}
|
|
85
147
|
function buildStyleGuidePrompt(styleSummary) {
|
|
86
148
|
return `You are a design system expert. Based on the following detected styles, generate a clean, organized style guide in Markdown format.
|
|
87
149
|
|
|
@@ -129,10 +191,12 @@ var OllamaClient = class {
|
|
|
129
191
|
baseUrl;
|
|
130
192
|
model;
|
|
131
193
|
timeout;
|
|
194
|
+
instrumentation;
|
|
132
195
|
constructor(options = {}) {
|
|
133
196
|
this.baseUrl = options.baseUrl || DEFAULT_BASE_URL;
|
|
134
197
|
this.model = options.model || UILINT_DEFAULT_OLLAMA_MODEL;
|
|
135
198
|
this.timeout = options.timeout || DEFAULT_TIMEOUT;
|
|
199
|
+
this.instrumentation = options.instrumentation;
|
|
136
200
|
}
|
|
137
201
|
/**
|
|
138
202
|
* Low-level completion API for custom prompts (used by installers/tools).
|
|
@@ -157,7 +221,12 @@ var OllamaClient = class {
|
|
|
157
221
|
const startTime = Date.now();
|
|
158
222
|
const prompt = buildAnalysisPrompt(styleSummary, styleGuide);
|
|
159
223
|
try {
|
|
160
|
-
const response = onProgress ? await this.generateStreaming(
|
|
224
|
+
const response = onProgress ? await this.generateStreaming(
|
|
225
|
+
prompt,
|
|
226
|
+
true,
|
|
227
|
+
onProgress,
|
|
228
|
+
"analyze-styles"
|
|
229
|
+
) : await this.generate(prompt, true, "analyze-styles");
|
|
161
230
|
const issues = this.parseIssuesResponse(response);
|
|
162
231
|
return {
|
|
163
232
|
issues,
|
|
@@ -179,7 +248,12 @@ var OllamaClient = class {
|
|
|
179
248
|
const startTime = Date.now();
|
|
180
249
|
const prompt = buildSourceAnalysisPrompt(source, styleGuide, options);
|
|
181
250
|
try {
|
|
182
|
-
const response = onProgress ? await this.generateStreaming(
|
|
251
|
+
const response = onProgress ? await this.generateStreaming(
|
|
252
|
+
prompt,
|
|
253
|
+
true,
|
|
254
|
+
onProgress,
|
|
255
|
+
"analyze-source"
|
|
256
|
+
) : await this.generate(prompt, true, "analyze-source");
|
|
183
257
|
const issues = this.parseIssuesResponse(response);
|
|
184
258
|
return {
|
|
185
259
|
issues,
|
|
@@ -199,7 +273,11 @@ var OllamaClient = class {
|
|
|
199
273
|
async generateStyleGuide(styleSummary) {
|
|
200
274
|
const prompt = buildStyleGuidePrompt(styleSummary);
|
|
201
275
|
try {
|
|
202
|
-
const response = await this.generate(
|
|
276
|
+
const response = await this.generate(
|
|
277
|
+
prompt,
|
|
278
|
+
false,
|
|
279
|
+
"generate-styleguide"
|
|
280
|
+
);
|
|
203
281
|
return response;
|
|
204
282
|
} catch (error) {
|
|
205
283
|
console.error("[UILint] Style guide generation failed:", error);
|
|
@@ -209,9 +287,15 @@ var OllamaClient = class {
|
|
|
209
287
|
/**
|
|
210
288
|
* Core generate method that calls Ollama API
|
|
211
289
|
*/
|
|
212
|
-
async generate(prompt, jsonFormat = true) {
|
|
290
|
+
async generate(prompt, jsonFormat = true, operationName = "ollama-generate") {
|
|
213
291
|
const controller = new AbortController();
|
|
214
292
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
293
|
+
const span = this.instrumentation?.onGenerationStart?.({
|
|
294
|
+
name: operationName,
|
|
295
|
+
model: this.model,
|
|
296
|
+
prompt,
|
|
297
|
+
metadata: { jsonFormat, stream: false }
|
|
298
|
+
});
|
|
215
299
|
try {
|
|
216
300
|
const response = await fetch(`${this.baseUrl}/api/generate`, {
|
|
217
301
|
method: "POST",
|
|
@@ -225,10 +309,21 @@ var OllamaClient = class {
|
|
|
225
309
|
signal: controller.signal
|
|
226
310
|
});
|
|
227
311
|
if (!response.ok) {
|
|
228
|
-
|
|
312
|
+
const error = `Ollama API error: ${response.status}`;
|
|
313
|
+
span?.end("", { error });
|
|
314
|
+
throw new Error(error);
|
|
229
315
|
}
|
|
230
316
|
const data = await response.json();
|
|
231
|
-
|
|
317
|
+
const output = data.response || "";
|
|
318
|
+
span?.end(output, {
|
|
319
|
+
promptTokens: data.prompt_eval_count,
|
|
320
|
+
completionTokens: data.eval_count,
|
|
321
|
+
totalTokens: (data.prompt_eval_count || 0) + (data.eval_count || 0) || void 0
|
|
322
|
+
});
|
|
323
|
+
return output;
|
|
324
|
+
} catch (error) {
|
|
325
|
+
span?.end("", { error: String(error) });
|
|
326
|
+
throw error;
|
|
232
327
|
} finally {
|
|
233
328
|
clearTimeout(timeoutId);
|
|
234
329
|
}
|
|
@@ -237,9 +332,17 @@ var OllamaClient = class {
|
|
|
237
332
|
* Streaming generate method that calls Ollama API with streaming
|
|
238
333
|
* and reports progress via callback
|
|
239
334
|
*/
|
|
240
|
-
async generateStreaming(prompt, jsonFormat = true, onProgress) {
|
|
335
|
+
async generateStreaming(prompt, jsonFormat = true, onProgress, operationName = "ollama-generate-stream") {
|
|
241
336
|
const controller = new AbortController();
|
|
242
337
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
338
|
+
const span = this.instrumentation?.onGenerationStart?.({
|
|
339
|
+
name: operationName,
|
|
340
|
+
model: this.model,
|
|
341
|
+
prompt,
|
|
342
|
+
metadata: { jsonFormat, stream: true }
|
|
343
|
+
});
|
|
344
|
+
let promptTokens;
|
|
345
|
+
let completionTokens;
|
|
243
346
|
try {
|
|
244
347
|
const response = await fetch(`${this.baseUrl}/api/generate`, {
|
|
245
348
|
method: "POST",
|
|
@@ -253,10 +356,14 @@ var OllamaClient = class {
|
|
|
253
356
|
signal: controller.signal
|
|
254
357
|
});
|
|
255
358
|
if (!response.ok) {
|
|
256
|
-
|
|
359
|
+
const error = `Ollama API error: ${response.status}`;
|
|
360
|
+
span?.end("", { error });
|
|
361
|
+
throw new Error(error);
|
|
257
362
|
}
|
|
258
363
|
if (!response.body) {
|
|
259
|
-
|
|
364
|
+
const error = "No response body for streaming";
|
|
365
|
+
span?.end("", { error });
|
|
366
|
+
throw new Error(error);
|
|
260
367
|
}
|
|
261
368
|
const reader = response.body.getReader();
|
|
262
369
|
const decoder = new TextDecoder();
|
|
@@ -278,6 +385,10 @@ var OllamaClient = class {
|
|
|
278
385
|
const latestLine = responseLines[responseLines.length - 1] || responseLines[responseLines.length - 2] || "";
|
|
279
386
|
onProgress(latestLine.trim(), fullResponse);
|
|
280
387
|
}
|
|
388
|
+
if (chunk.done) {
|
|
389
|
+
promptTokens = chunk.prompt_eval_count;
|
|
390
|
+
completionTokens = chunk.eval_count;
|
|
391
|
+
}
|
|
281
392
|
} catch {
|
|
282
393
|
}
|
|
283
394
|
}
|
|
@@ -288,10 +399,22 @@ var OllamaClient = class {
|
|
|
288
399
|
if (chunk.response) {
|
|
289
400
|
fullResponse += chunk.response;
|
|
290
401
|
}
|
|
402
|
+
if (chunk.done) {
|
|
403
|
+
promptTokens = chunk.prompt_eval_count;
|
|
404
|
+
completionTokens = chunk.eval_count;
|
|
405
|
+
}
|
|
291
406
|
} catch {
|
|
292
407
|
}
|
|
293
408
|
}
|
|
409
|
+
span?.end(fullResponse, {
|
|
410
|
+
promptTokens,
|
|
411
|
+
completionTokens,
|
|
412
|
+
totalTokens: (promptTokens || 0) + (completionTokens || 0) || void 0
|
|
413
|
+
});
|
|
294
414
|
return fullResponse;
|
|
415
|
+
} catch (error) {
|
|
416
|
+
span?.end("", { error: String(error) });
|
|
417
|
+
throw error;
|
|
295
418
|
} finally {
|
|
296
419
|
clearTimeout(timeoutId);
|
|
297
420
|
}
|
|
@@ -334,6 +457,19 @@ var OllamaClient = class {
|
|
|
334
457
|
setModel(model) {
|
|
335
458
|
this.model = model;
|
|
336
459
|
}
|
|
460
|
+
/**
|
|
461
|
+
* Sets instrumentation callbacks for observability.
|
|
462
|
+
* This allows configuring instrumentation after construction.
|
|
463
|
+
*/
|
|
464
|
+
setInstrumentation(instrumentation) {
|
|
465
|
+
this.instrumentation = instrumentation;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Returns true if instrumentation is currently configured.
|
|
469
|
+
*/
|
|
470
|
+
hasInstrumentation() {
|
|
471
|
+
return !!this.instrumentation;
|
|
472
|
+
}
|
|
337
473
|
};
|
|
338
474
|
var defaultClient = null;
|
|
339
475
|
function getOllamaClient(options) {
|
|
@@ -1165,8 +1301,10 @@ function validateViolations(violations) {
|
|
|
1165
1301
|
if (typeof obj.severity !== "string") return false;
|
|
1166
1302
|
if (typeof obj.message !== "string") return false;
|
|
1167
1303
|
if (!obj.details || typeof obj.details !== "object") return false;
|
|
1168
|
-
if (!VALID_CATEGORIES.includes(obj.category))
|
|
1169
|
-
|
|
1304
|
+
if (!VALID_CATEGORIES.includes(obj.category))
|
|
1305
|
+
return false;
|
|
1306
|
+
if (!VALID_SEVERITIES.includes(obj.severity))
|
|
1307
|
+
return false;
|
|
1170
1308
|
return true;
|
|
1171
1309
|
}).map((v) => ({
|
|
1172
1310
|
elementIds: v.elementIds,
|
|
@@ -1207,8 +1345,10 @@ function formatConsistencyViolations(violations) {
|
|
|
1207
1345
|
if (violations.length === 0) {
|
|
1208
1346
|
return "\u2713 No consistency issues found.";
|
|
1209
1347
|
}
|
|
1210
|
-
const lines = [
|
|
1211
|
-
`
|
|
1348
|
+
const lines = [
|
|
1349
|
+
`Found ${violations.length} consistency issue(s):
|
|
1350
|
+
`
|
|
1351
|
+
];
|
|
1212
1352
|
const severityIcons = {
|
|
1213
1353
|
error: "\u2716",
|
|
1214
1354
|
warning: "\u26A0",
|
|
@@ -1235,6 +1375,7 @@ function formatConsistencyViolations(violations) {
|
|
|
1235
1375
|
export {
|
|
1236
1376
|
buildAnalysisPrompt,
|
|
1237
1377
|
buildSourceAnalysisPrompt,
|
|
1378
|
+
buildSourceScanPrompt,
|
|
1238
1379
|
buildStyleGuidePrompt,
|
|
1239
1380
|
UILINT_DEFAULT_OLLAMA_MODEL,
|
|
1240
1381
|
OllamaClient,
|
|
@@ -1269,4 +1410,4 @@ export {
|
|
|
1269
1410
|
analyzeConsistency,
|
|
1270
1411
|
formatConsistencyViolations
|
|
1271
1412
|
};
|
|
1272
|
-
//# sourceMappingURL=chunk-
|
|
1413
|
+
//# sourceMappingURL=chunk-FIESH4VM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ollama/prompts.ts","../src/ollama/defaults.ts","../src/ollama/client.ts","../src/format/issues.ts","../src/tailwind/class-tokens.ts","../src/scanner/style-extractor.ts","../src/styleguide/schema.ts","../src/styleguide/parser.ts","../src/styleguide/generator.ts","../src/consistency/prompts.ts","../src/consistency/analyzer.ts"],"sourcesContent":["/**\n * LLM prompt builders for UILint analysis\n */\n\n/**\n * Builds a prompt for style analysis\n */\nexport function buildAnalysisPrompt(\n styleSummary: string,\n styleGuide: string | null\n): string {\n const guideSection = styleGuide\n ? `## Current Style Guide\\n${styleGuide}\\n\\n`\n : \"## No Style Guide Found\\nAnalyze the styles and identify inconsistencies.\\n\\n\";\n\n return `You are a UI consistency analyzer. Analyze the following extracted styles and identify UI consistency violations.\n\n${guideSection}\n\n${styleSummary}\n\nRespond with JSON ONLY. Return a single JSON object containing an \"issues\" array. Each issue should have:\n- id: unique string identifier\n- type: one of \"color\", \"typography\", \"spacing\", \"component\", \"responsive\", \"accessibility\"\n- message: human-readable description of the issue\n- currentValue: the problematic value found\n- expectedValue: what it should be (if known from style guide)\n\nIMPORTANT:\n- Output ONLY violations. Do NOT include fix instructions, remediation steps, or \"suggestions\".\n- Do NOT include extra top-level keys. Only return {\"issues\":[...]}.\n- Keep messages short and neutral.\n\nFocus on:\n1. Similar but not identical colors (e.g., #3B82F6 vs #3575E2)\n2. Inconsistent font sizes or weights\n3. Spacing values that don't follow a consistent scale (e.g., 4px base unit)\n4. Mixed border-radius values\n5. If utility/Tailwind classes are present in the summary, treat them as the styling surface area and flag inconsistent utility usage (e.g., mixing px-4 and px-5 for the same component type)\n\nBe minimal. Only report significant inconsistencies.\n\nExample response:\n{\n \"issues\": [\n {\n ...issue fields...\n }\n ]\n}`;\n}\n\nexport interface BuildSourceAnalysisPromptOptions {\n /**\n * Optional filename/path for extra context in the prompt.\n */\n filePath?: string;\n /**\n * Optional hint about language (e.g. tsx, jsx, html).\n */\n languageHint?: string;\n /**\n * Optional additional context (e.g., Tailwind tokens, extracted utilities).\n * Keep this concise; it's appended verbatim.\n */\n extraContext?: string;\n}\n\n/**\n * Builds a prompt for analyzing a raw source file/snippet (TSX/JSX/etc) directly,\n * without attempting to parse it as HTML/DOM.\n */\nexport function buildSourceAnalysisPrompt(\n source: string,\n styleGuide: string | null,\n options: BuildSourceAnalysisPromptOptions = {}\n): string {\n const guideSection = styleGuide\n ? `## Current Style Guide\\n${styleGuide}\\n\\n`\n : \"## No Style Guide Found\\nAnalyze the UI code and identify inconsistencies.\\n\\n\";\n\n const metaLines: string[] = [];\n if (options.filePath) metaLines.push(`- filePath: ${options.filePath}`);\n if (options.languageHint)\n metaLines.push(`- languageHint: ${options.languageHint}`);\n const metaSection =\n metaLines.length > 0\n ? `## Source Metadata\\n${metaLines.join(\"\\n\")}\\n\\n`\n : \"\";\n\n const extra =\n options.extraContext && options.extraContext.trim()\n ? `## Additional Context\\n${options.extraContext.trim()}\\n\\n`\n : \"\";\n\n return `You are a UI consistency analyzer. Analyze the following UI source code and identify UI consistency violations.\n\n${guideSection}${metaSection}${extra}## Source Code (raw)\n${source}\n\nRespond with JSON ONLY. Return a single JSON object containing an \"issues\" array. Each issue should have:\n- id: unique string identifier\n- type: one of \"color\", \"typography\", \"spacing\", \"component\", \"responsive\", \"accessibility\"\n- message: human-readable description of the issue\n- currentValue: the problematic value found\n- expectedValue: what it should be (if known from style guide)\n\nIMPORTANT:\n- Output ONLY violations. Do NOT include fix instructions, remediation steps, or \"suggestions\".\n- Do NOT include extra top-level keys. Only return {\"issues\":[...]}.\n- Keep messages short and neutral.\n\nFocus on:\n1. Inconsistent Tailwind/utility classes (e.g., mixing px-4 and px-5 for the same component type)\n2. Inconsistent component variants (e.g., button sizes/radii/typography drift)\n3. Hardcoded colors/spacing/typography that should use tokens/scale\n4. Accessibility issues visible from code (e.g., missing labels, low-contrast combos if obvious)\n\nBe minimal. Only report significant inconsistencies.`;\n}\n\nexport interface BuildSourceScanPromptOptions {\n /**\n * Display filename/path for context in the prompt.\n */\n filePath?: string;\n /**\n * Optional focus target for manual scan UX.\n */\n componentName?: string;\n componentLine?: number;\n /**\n * If true, the scan is for the selected element + children (UI text only).\n */\n includeChildren?: boolean;\n /**\n * Optional list of data-loc values (format: \"path:line:column\") that the model MUST\n * restrict issues to (so UI can highlight exactly).\n */\n dataLocs?: string[];\n}\n\n/**\n * Builds a prompt for scanning a source file/snippet for issues that can be mapped\n * back to rendered DOM elements via `data-loc`.\n *\n * Response schema (JSON):\n * { \"issues\": [{ \"line\"?: number, \"message\": string, \"dataLoc\"?: string }] }\n */\nexport function buildSourceScanPrompt(\n sourceCode: string,\n styleGuide: string | null,\n options: BuildSourceScanPromptOptions = {}\n): string {\n const fileLabel = options.filePath || \"component.tsx\";\n const dataLocList = Array.isArray(options.dataLocs) ? options.dataLocs : [];\n\n const focusSection =\n options.componentName && options.componentName.trim()\n ? `## Focus\n\nFocus on the \"${options.componentName.trim()}\" component${\n typeof options.componentLine === \"number\"\n ? ` (near line ${options.componentLine})`\n : \"\"\n }.\n\n`\n : \"\";\n\n const scopeText =\n options.includeChildren === true\n ? \"Scope: selected element + children.\"\n : \"Scope: selected element only.\";\n\n const dataLocSection =\n dataLocList.length > 0\n ? `## Element Locations (data-loc values)\n\nThe following elements are rendered on the page from this file. Each data-loc has format \"path:line:column\".\nWhen reporting issues, include the matching dataLoc value so we can highlight the specific element.\n\n\\`\\`\\`\n${dataLocList.join(\"\\n\")}\n\\`\\`\\`\n\n`\n : \"\";\n\n const guideSection = styleGuide\n ? `## Style Guide\n\n${styleGuide}\n\n`\n : \"\";\n\n return `You are a UI code reviewer. Analyze the following React/TypeScript UI code for style consistency issues.\n\n${guideSection}${focusSection}${scopeText}\n\n${dataLocSection}## Source Code (${fileLabel})\n\n\\`\\`\\`tsx\n${sourceCode}\n\\`\\`\\`\n\n## Task\n\nIdentify any style inconsistencies, violations of best practices, or deviations from the style guide.\nFor each issue, provide:\n- line: the line number in the source file\n- message: a clear description of the issue\n- dataLoc: the matching data-loc value from the list above (if applicable, to identify the specific element)\n\n## Critical Requirements for dataLoc\n\n- If \"Element Locations (data-loc values)\" are provided, ONLY report issues that correspond to one of those elements.\n- When you include a dataLoc, it MUST be copied verbatim from the list (no shortening paths, no normalization).\n- If you cannot match an issue to a provided dataLoc, omit that issue from the response.\n\nRespond with JSON:\n\\`\\`\\`json\n{\n \"issues\": [\n { \"line\": 12, \"message\": \"Color #3575E2 should be #3B82F6 (primary blue from styleguide)\", \"dataLoc\": \"app/page.tsx:12:5\" }\n ]\n}\n\\`\\`\\`\n\nIf no issues are found, respond with:\n\\`\\`\\`json\n{ \"issues\": [] }\n\\`\\`\\``;\n}\n\n/**\n * Builds a prompt for style guide generation\n */\nexport function buildStyleGuidePrompt(styleSummary: string): string {\n return `You are a design system expert. Based on the following detected styles, generate a clean, organized style guide in Markdown format.\n\n${styleSummary}\n\nGenerate a style guide with these sections:\n1. Colors - List the main colors with semantic names (Primary, Secondary, etc.)\n2. Typography - Font families, sizes, and weights\n3. Spacing - Base unit and common spacing values\n4. Components - Common component patterns\n5. Tailwind (if utility classes are present) - list commonly used utilities and any relevant theme tokens\n\nUse this format:\n# UI Style Guide\n\n## Colors\n- **Primary**: #HEXCODE (usage description)\n- **Secondary**: #HEXCODE (usage description)\n...\n\n## Typography\n- **Font Family**: FontName\n- **Font Sizes**: list of sizes\n- **Font Weights**: list of weights\n\n## Spacing\n- **Base unit**: Xpx\n- **Common values**: list of values\n\n## Components\n- **Buttons**: styles\n- **Cards**: styles\n...\n\nBe concise and focus on the most used values.`;\n}\n","/**\n * Single source of truth for Ollama defaults used across the monorepo.\n */\n\n/**\n * Default Ollama model used when none is provided.\n *\n * Note: This should match the model we recommend users `ollama pull`.\n */\nexport const UILINT_DEFAULT_OLLAMA_MODEL = \"qwen3-coder:30b\";\n","/**\n * Ollama API client for LLM interactions\n */\n\nimport type {\n UILintIssue,\n AnalysisResult,\n OllamaClientOptions,\n StreamProgressCallback,\n LLMInstrumentationCallbacks,\n} from \"../types.js\";\nimport {\n buildAnalysisPrompt,\n buildSourceAnalysisPrompt,\n buildStyleGuidePrompt,\n type BuildSourceAnalysisPromptOptions,\n} from \"./prompts.js\";\nimport { UILINT_DEFAULT_OLLAMA_MODEL } from \"./defaults.js\";\n\nconst DEFAULT_BASE_URL = \"http://localhost:11434\";\nconst DEFAULT_TIMEOUT = 60000;\n\nexport class OllamaClient {\n private baseUrl: string;\n private model: string;\n private timeout: number;\n private instrumentation?: LLMInstrumentationCallbacks;\n\n constructor(options: OllamaClientOptions = {}) {\n this.baseUrl = options.baseUrl || DEFAULT_BASE_URL;\n this.model = options.model || UILINT_DEFAULT_OLLAMA_MODEL;\n this.timeout = options.timeout || DEFAULT_TIMEOUT;\n this.instrumentation = options.instrumentation;\n }\n\n /**\n * Low-level completion API for custom prompts (used by installers/tools).\n *\n * When `json` is true, Ollama is requested to emit JSON (best-effort).\n */\n async complete(\n prompt: string,\n options: {\n json?: boolean;\n stream?: boolean;\n onProgress?: StreamProgressCallback;\n } = {}\n ): Promise<string> {\n const jsonFormat = options.json ?? false;\n if (options.stream && options.onProgress) {\n return await this.generateStreaming(\n prompt,\n jsonFormat,\n options.onProgress\n );\n }\n return await this.generate(prompt, jsonFormat);\n }\n\n /**\n * Analyzes styles and returns issues\n */\n async analyzeStyles(\n styleSummary: string,\n styleGuide: string | null,\n onProgress?: StreamProgressCallback\n ): Promise<AnalysisResult> {\n const startTime = Date.now();\n const prompt = buildAnalysisPrompt(styleSummary, styleGuide);\n\n try {\n const response = onProgress\n ? await this.generateStreaming(\n prompt,\n true,\n onProgress,\n \"analyze-styles\"\n )\n : await this.generate(prompt, true, \"analyze-styles\");\n const issues = this.parseIssuesResponse(response);\n\n return {\n issues,\n analysisTime: Date.now() - startTime,\n };\n } catch (error) {\n console.error(\"[UILint] Analysis failed:\", error);\n return {\n issues: [],\n analysisTime: Date.now() - startTime,\n };\n }\n }\n\n /**\n * Analyzes a raw source file/snippet (TSX/JSX/etc) directly and returns issues.\n * This bypasses HTML/DOM parsing entirely.\n */\n async analyzeSource(\n source: string,\n styleGuide: string | null,\n onProgress?: StreamProgressCallback,\n options: BuildSourceAnalysisPromptOptions = {}\n ): Promise<AnalysisResult> {\n const startTime = Date.now();\n const prompt = buildSourceAnalysisPrompt(source, styleGuide, options);\n\n try {\n const response = onProgress\n ? await this.generateStreaming(\n prompt,\n true,\n onProgress,\n \"analyze-source\"\n )\n : await this.generate(prompt, true, \"analyze-source\");\n const issues = this.parseIssuesResponse(response);\n\n return {\n issues,\n analysisTime: Date.now() - startTime,\n };\n } catch (error) {\n console.error(\"[UILint] Analysis failed:\", error);\n return {\n issues: [],\n analysisTime: Date.now() - startTime,\n };\n }\n }\n\n /**\n * Generates a style guide from detected styles\n */\n async generateStyleGuide(styleSummary: string): Promise<string | null> {\n const prompt = buildStyleGuidePrompt(styleSummary);\n\n try {\n const response = await this.generate(\n prompt,\n false,\n \"generate-styleguide\"\n );\n return response;\n } catch (error) {\n console.error(\"[UILint] Style guide generation failed:\", error);\n return null;\n }\n }\n\n /**\n * Core generate method that calls Ollama API\n */\n private async generate(\n prompt: string,\n jsonFormat: boolean = true,\n operationName: string = \"ollama-generate\"\n ): Promise<string> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n // Start instrumentation span if available\n const span = this.instrumentation?.onGenerationStart?.({\n name: operationName,\n model: this.model,\n prompt,\n metadata: { jsonFormat, stream: false },\n });\n\n try {\n const response = await fetch(`${this.baseUrl}/api/generate`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n model: this.model,\n prompt,\n stream: false,\n ...(jsonFormat && { format: \"json\" }),\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const error = `Ollama API error: ${response.status}`;\n span?.end(\"\", { error });\n throw new Error(error);\n }\n\n const data = await response.json();\n const output = data.response || \"\";\n\n // End instrumentation span with output and usage\n span?.end(output, {\n promptTokens: data.prompt_eval_count,\n completionTokens: data.eval_count,\n totalTokens:\n (data.prompt_eval_count || 0) + (data.eval_count || 0) || undefined,\n });\n\n return output;\n } catch (error) {\n span?.end(\"\", { error: String(error) });\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Streaming generate method that calls Ollama API with streaming\n * and reports progress via callback\n */\n private async generateStreaming(\n prompt: string,\n jsonFormat: boolean = true,\n onProgress: StreamProgressCallback,\n operationName: string = \"ollama-generate-stream\"\n ): Promise<string> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n // Start instrumentation span if available\n const span = this.instrumentation?.onGenerationStart?.({\n name: operationName,\n model: this.model,\n prompt,\n metadata: { jsonFormat, stream: true },\n });\n\n // Track token counts from streaming response\n let promptTokens: number | undefined;\n let completionTokens: number | undefined;\n\n try {\n const response = await fetch(`${this.baseUrl}/api/generate`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n model: this.model,\n prompt,\n stream: true,\n ...(jsonFormat && { format: \"json\" }),\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const error = `Ollama API error: ${response.status}`;\n span?.end(\"\", { error });\n throw new Error(error);\n }\n\n if (!response.body) {\n const error = \"No response body for streaming\";\n span?.end(\"\", { error });\n throw new Error(error);\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let fullResponse = \"\";\n let buffer = \"\";\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n\n // Process complete JSON lines from the buffer\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\"; // Keep incomplete line in buffer\n\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const chunk = JSON.parse(line);\n if (chunk.response) {\n fullResponse += chunk.response;\n // Get the latest line from the response for progress display\n const responseLines = fullResponse.split(\"\\n\");\n const latestLine =\n responseLines[responseLines.length - 1] ||\n responseLines[responseLines.length - 2] ||\n \"\";\n onProgress(latestLine.trim(), fullResponse);\n }\n // Capture token counts from final chunk\n if (chunk.done) {\n promptTokens = chunk.prompt_eval_count;\n completionTokens = chunk.eval_count;\n }\n } catch {\n // Skip malformed JSON chunks\n }\n }\n }\n\n // Process any remaining content in buffer\n if (buffer.trim()) {\n try {\n const chunk = JSON.parse(buffer);\n if (chunk.response) {\n fullResponse += chunk.response;\n }\n if (chunk.done) {\n promptTokens = chunk.prompt_eval_count;\n completionTokens = chunk.eval_count;\n }\n } catch {\n // Skip malformed JSON\n }\n }\n\n // End instrumentation span with output and usage\n span?.end(fullResponse, {\n promptTokens,\n completionTokens,\n totalTokens: (promptTokens || 0) + (completionTokens || 0) || undefined,\n });\n\n return fullResponse;\n } catch (error) {\n span?.end(\"\", { error: String(error) });\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Parses issues from LLM response\n */\n private parseIssuesResponse(response: string): UILintIssue[] {\n try {\n const parsed = JSON.parse(response);\n return parsed.issues || [];\n } catch {\n console.warn(\"[UILint] Failed to parse LLM response as JSON\");\n return [];\n }\n }\n\n /**\n * Checks if Ollama is available\n */\n async isAvailable(): Promise<boolean> {\n try {\n const response = await fetch(`${this.baseUrl}/api/tags`, {\n method: \"GET\",\n signal: AbortSignal.timeout(5000),\n });\n return response.ok;\n } catch {\n return false;\n }\n }\n\n /**\n * Gets the current model\n */\n getModel(): string {\n return this.model;\n }\n\n /**\n * Sets the model\n */\n setModel(model: string): void {\n this.model = model;\n }\n\n /**\n * Sets instrumentation callbacks for observability.\n * This allows configuring instrumentation after construction.\n */\n setInstrumentation(\n instrumentation: LLMInstrumentationCallbacks | undefined\n ): void {\n this.instrumentation = instrumentation;\n }\n\n /**\n * Returns true if instrumentation is currently configured.\n */\n hasInstrumentation(): boolean {\n return !!this.instrumentation;\n }\n}\n\n// Default singleton instance\nlet defaultClient: OllamaClient | null = null;\n\nexport function getOllamaClient(options?: OllamaClientOptions): OllamaClient {\n if (!defaultClient || options) {\n defaultClient = new OllamaClient(options);\n }\n return defaultClient;\n}\n","import type { UILintIssue } from \"../types.js\";\n\nexport interface FormatViolationsOptions {\n /**\n * Optional context label (e.g., filename) to include as a single header line.\n * Keep this empty for the most minimal output.\n */\n context?: string;\n /**\n * Include the trailing \"consult the style guide\" message.\n * Defaults to true.\n */\n includeFooter?: boolean;\n /**\n * Override the footer message.\n */\n footerMessage?: string;\n}\n\nconst DEFAULT_FOOTER = \"Consult the style guide for guidance.\";\n\n/**\n * Ensures issues are safe/minimal to print or serialize:\n * - drops `suggestion` (we only want violations)\n * - trims string fields\n */\nexport function sanitizeIssues(issues: UILintIssue[]): UILintIssue[] {\n return issues.map((issue) => ({\n id: String(issue.id ?? \"\").trim(),\n type: issue.type,\n message: String(issue.message ?? \"\").trim(),\n element: issue.element ? String(issue.element).trim() : undefined,\n selector: issue.selector ? String(issue.selector).trim() : undefined,\n currentValue: issue.currentValue ? String(issue.currentValue).trim() : undefined,\n expectedValue: issue.expectedValue\n ? String(issue.expectedValue).trim()\n : undefined,\n // Intentionally omit `suggestion`\n }));\n}\n\n/**\n * Minimal human-readable rendering of violations.\n * Intended for CLI/MCP text output.\n */\nexport function formatViolationsText(\n issues: UILintIssue[],\n options: FormatViolationsOptions = {}\n): string {\n const { context, includeFooter = true, footerMessage = DEFAULT_FOOTER } =\n options;\n\n if (!issues || issues.length === 0) {\n return \"No violations found.\";\n }\n\n const lines: string[] = [];\n\n if (context && context.trim()) {\n lines.push(`Violations in ${context.trim()}:`);\n lines.push(\"\");\n }\n\n const sanitized = sanitizeIssues(issues);\n sanitized.forEach((issue, i) => {\n lines.push(`${i + 1}. [${issue.type}] ${issue.message}`);\n if (issue.currentValue && issue.expectedValue) {\n lines.push(` ${issue.currentValue} → ${issue.expectedValue}`);\n } else if (issue.currentValue) {\n lines.push(` ${issue.currentValue}`);\n }\n lines.push(\"\");\n });\n\n // remove trailing blank line\n while (lines.length > 0 && lines[lines.length - 1] === \"\") lines.pop();\n\n if (includeFooter) {\n lines.push(\"\");\n lines.push(footerMessage);\n }\n\n return lines.join(\"\\n\");\n}\n","/**\n * Tailwind / utility-class token extraction helpers.\n *\n * Notes:\n * - Works on HTML (class=\"...\") and TSX-ish input (className=\"...\") via regex.\n * - Variant parsing is bracket-aware so arbitrary values like bg-[color:var(--x)]\n * don't get split incorrectly on \":\".\n */\n\nexport interface ClassTokenCounts {\n /**\n * Base utilities with variants stripped.\n * Example: \"sm:hover:bg-gray-50\" => utility \"bg-gray-50\"\n */\n utilities: Map<string, number>;\n /**\n * Individual variant prefixes.\n * Example: \"sm:hover:bg-gray-50\" => variants \"sm\", \"hover\"\n */\n variants: Map<string, number>;\n}\n\nexport interface ExtractClassTokenOptions {\n /**\n * Maximum number of tokens to process (guardrail for very large inputs).\n */\n maxTokens?: number;\n}\n\nexport function extractClassTokensFromHtml(\n html: string,\n options: ExtractClassTokenOptions = {}\n): ClassTokenCounts {\n const { maxTokens = 20000 } = options;\n\n const utilities = new Map<string, number>();\n const variants = new Map<string, number>();\n\n if (!html) return { utilities, variants };\n\n // Match both HTML and JSX-ish attributes.\n // - class=\"...\"\n // - className=\"...\"\n const attrPattern = /\\b(?:class|className)\\s*=\\s*[\"']([^\"']+)[\"']/g;\n\n let tokenBudget = maxTokens;\n let match: RegExpExecArray | null;\n while ((match = attrPattern.exec(html)) && tokenBudget > 0) {\n const raw = match[1];\n if (!raw) continue;\n\n const tokens = raw.split(/\\s+/g).filter(Boolean);\n for (const token of tokens) {\n if (tokenBudget-- <= 0) break;\n\n const { base, variantList } = splitVariants(token);\n const normalizedBase = normalizeUtility(base);\n if (!normalizedBase) continue;\n\n increment(utilities, normalizedBase);\n for (const v of variantList) increment(variants, v);\n }\n }\n\n return { utilities, variants };\n}\n\nexport function topEntries(\n map: Map<string, number>,\n limit: number\n): Array<{ token: string; count: number }> {\n return [...map.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, limit)\n .map(([token, count]) => ({ token, count }));\n}\n\nfunction increment(map: Map<string, number>, key: string): void {\n map.set(key, (map.get(key) || 0) + 1);\n}\n\nfunction normalizeUtility(token: string): string | null {\n const t = token.trim();\n if (!t) return null;\n\n // Strip important modifier\n const noImportant = t.startsWith(\"!\") ? t.slice(1) : t;\n\n // Ignore obviously non-class tokens\n if (!noImportant || noImportant === \"{\" || noImportant === \"}\") return null;\n\n return noImportant;\n}\n\n/**\n * Splits a class token into variants and base utility.\n * Bracket-aware to avoid splitting arbitrary values on \":\".\n *\n * Examples:\n * - \"sm:hover:bg-gray-50\" => variants [\"sm\",\"hover\"], base \"bg-gray-50\"\n * - \"bg-[color:var(--x)]\" => variants [], base \"bg-[color:var(--x)]\"\n * - \"sm:bg-[color:var(--x)]\" => variants [\"sm\"], base \"bg-[color:var(--x)]\"\n */\nfunction splitVariants(token: string): { base: string; variantList: string[] } {\n const parts: string[] = [];\n let buf = \"\";\n let bracketDepth = 0;\n\n for (let i = 0; i < token.length; i++) {\n const ch = token[i];\n if (ch === \"[\") bracketDepth++;\n if (ch === \"]\" && bracketDepth > 0) bracketDepth--;\n\n if (ch === \":\" && bracketDepth === 0) {\n parts.push(buf);\n buf = \"\";\n continue;\n }\n\n buf += ch;\n }\n parts.push(buf);\n\n if (parts.length <= 1) return { base: token, variantList: [] };\n\n const base = parts[parts.length - 1] || \"\";\n const variantList = parts\n .slice(0, -1)\n .map((v) => v.trim())\n .filter(Boolean);\n\n return { base, variantList };\n}\n","/**\n * Style extraction from DOM elements\n */\n\nimport type {\n ExtractedStyles,\n SerializedStyles,\n TailwindThemeTokens,\n} from \"../types.js\";\nimport {\n extractClassTokensFromHtml,\n topEntries,\n} from \"../tailwind/class-tokens.js\";\n\n/**\n * Extracts all computed styles from elements in the document\n * Works in both browser and JSDOM environments\n */\nexport function extractStyles(\n root: Element | Document,\n getComputedStyle: (el: Element) => CSSStyleDeclaration\n): ExtractedStyles {\n const styles: ExtractedStyles = {\n colors: new Map(),\n fontSizes: new Map(),\n fontFamilies: new Map(),\n fontWeights: new Map(),\n spacing: new Map(),\n borderRadius: new Map(),\n };\n\n const elements = root.querySelectorAll(\"*\");\n\n elements.forEach((element) => {\n // nodeType === 1 means Element node (works in both browser and JSDOM)\n if (element.nodeType !== 1) return;\n\n const computed = getComputedStyle(element);\n\n // Extract colors\n extractColor(computed.color, styles.colors);\n extractColor(computed.backgroundColor, styles.colors);\n extractColor(computed.borderColor, styles.colors);\n\n // Extract typography\n incrementMap(styles.fontSizes, computed.fontSize);\n incrementMap(styles.fontFamilies, normalizeFontFamily(computed.fontFamily));\n incrementMap(styles.fontWeights, computed.fontWeight);\n\n // Extract spacing\n extractSpacing(computed.margin, styles.spacing);\n extractSpacing(computed.padding, styles.spacing);\n incrementMap(styles.spacing, computed.gap);\n\n // Extract border radius\n incrementMap(styles.borderRadius, computed.borderRadius);\n });\n\n return styles;\n}\n\n/**\n * Extracts styles from browser DOM (uses window.getComputedStyle)\n */\nexport function extractStylesFromDOM(\n root?: Element | Document\n): ExtractedStyles {\n const targetRoot = root || document.body;\n return extractStyles(targetRoot, (el) => window.getComputedStyle(el));\n}\n\nfunction extractColor(color: string, map: Map<string, number>): void {\n if (!color || color === \"transparent\" || color === \"rgba(0, 0, 0, 0)\") return;\n\n // Normalize to hex\n const hex = rgbToHex(color);\n if (hex) {\n incrementMap(map, hex);\n }\n}\n\nfunction extractSpacing(value: string, map: Map<string, number>): void {\n if (!value || value === \"0px\") return;\n\n // Split compound values (e.g., \"10px 20px 10px 20px\")\n const values = value.split(\" \").filter((v) => v && v !== \"0px\");\n values.forEach((v) => incrementMap(map, v));\n}\n\nfunction incrementMap(map: Map<string, number>, value: string): void {\n if (!value || value === \"normal\" || value === \"auto\") return;\n map.set(value, (map.get(value) || 0) + 1);\n}\n\nfunction normalizeFontFamily(fontFamily: string): string {\n // Get the primary font (first in the stack)\n const primary = fontFamily.split(\",\")[0].trim();\n return primary.replace(/['\"]/g, \"\");\n}\n\nfunction rgbToHex(rgb: string): string | null {\n // Handle hex values\n if (rgb.startsWith(\"#\")) return rgb.toUpperCase();\n\n // Handle rgb/rgba values\n const match = rgb.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/);\n if (!match) return null;\n\n const [, r, g, b] = match;\n const toHex = (n: string) => parseInt(n).toString(16).padStart(2, \"0\");\n\n return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase();\n}\n\n/**\n * Converts ExtractedStyles maps to plain objects for serialization\n */\nexport function serializeStyles(styles: ExtractedStyles): SerializedStyles {\n return {\n colors: Object.fromEntries(styles.colors),\n fontSizes: Object.fromEntries(styles.fontSizes),\n fontFamilies: Object.fromEntries(styles.fontFamilies),\n fontWeights: Object.fromEntries(styles.fontWeights),\n spacing: Object.fromEntries(styles.spacing),\n borderRadius: Object.fromEntries(styles.borderRadius),\n };\n}\n\n/**\n * Converts SerializedStyles back to ExtractedStyles\n */\nexport function deserializeStyles(\n serialized: SerializedStyles\n): ExtractedStyles {\n return {\n colors: new Map(Object.entries(serialized.colors)),\n fontSizes: new Map(Object.entries(serialized.fontSizes)),\n fontFamilies: new Map(Object.entries(serialized.fontFamilies)),\n fontWeights: new Map(Object.entries(serialized.fontWeights)),\n spacing: new Map(Object.entries(serialized.spacing)),\n borderRadius: new Map(Object.entries(serialized.borderRadius)),\n };\n}\n\n/**\n * Creates a summary of extracted styles for LLM analysis\n */\nexport function createStyleSummary(\n styles: ExtractedStyles,\n options: CreateStyleSummaryOptions = {}\n): string {\n return createStyleSummaryWithOptions(styles, options);\n}\n\nexport interface CreateStyleSummaryOptions {\n /**\n * Optional HTML/TSX-ish string used to extract utility classes (Tailwind etc).\n */\n html?: string;\n /**\n * Optional Tailwind theme tokens (typically from tailwind.config.*).\n */\n tailwindTheme?: TailwindThemeTokens | null;\n}\n\n/**\n * Creates a summary of extracted styles for LLM analysis.\n * Accepts optional Tailwind context to make Tailwind-heavy projects analyzable\n * even when computed styles are sparse (e.g., JSDOM without loaded CSS).\n */\nexport function createStyleSummaryWithOptions(\n styles: ExtractedStyles,\n options: CreateStyleSummaryOptions = {}\n): string {\n const lines: string[] = [];\n\n lines.push(\"## Detected Styles Summary\\n\");\n\n // Colors\n lines.push(\"### Colors\");\n const sortedColors = [...styles.colors.entries()].sort((a, b) => b[1] - a[1]);\n sortedColors.slice(0, 20).forEach(([color, count]) => {\n lines.push(`- ${color}: ${count} occurrences`);\n });\n lines.push(\"\");\n\n // Font sizes\n lines.push(\"### Font Sizes\");\n const sortedFontSizes = [...styles.fontSizes.entries()].sort(\n (a, b) => b[1] - a[1]\n );\n sortedFontSizes.forEach(([size, count]) => {\n lines.push(`- ${size}: ${count} occurrences`);\n });\n lines.push(\"\");\n\n // Font families\n lines.push(\"### Font Families\");\n const sortedFontFamilies = [...styles.fontFamilies.entries()].sort(\n (a, b) => b[1] - a[1]\n );\n sortedFontFamilies.forEach(([family, count]) => {\n lines.push(`- ${family}: ${count} occurrences`);\n });\n lines.push(\"\");\n\n // Font weights\n lines.push(\"### Font Weights\");\n const sortedFontWeights = [...styles.fontWeights.entries()].sort(\n (a, b) => b[1] - a[1]\n );\n sortedFontWeights.forEach(([weight, count]) => {\n lines.push(`- ${weight}: ${count} occurrences`);\n });\n lines.push(\"\");\n\n // Spacing\n lines.push(\"### Spacing Values\");\n const sortedSpacing = [...styles.spacing.entries()].sort(\n (a, b) => b[1] - a[1]\n );\n sortedSpacing.slice(0, 15).forEach(([value, count]) => {\n lines.push(`- ${value}: ${count} occurrences`);\n });\n lines.push(\"\");\n\n // Border radius\n lines.push(\"### Border Radius\");\n const sortedBorderRadius = [...styles.borderRadius.entries()].sort(\n (a, b) => b[1] - a[1]\n );\n sortedBorderRadius.forEach(([value, count]) => {\n lines.push(`- ${value}: ${count} occurrences`);\n });\n\n // Tailwind / utility classes (optional)\n if (options.html) {\n const tokens = extractClassTokensFromHtml(options.html);\n const topUtilities = topEntries(tokens.utilities, 40);\n const topVariants = topEntries(tokens.variants, 15);\n\n lines.push(\"\");\n lines.push(\"### Utility Classes (from markup)\");\n if (topUtilities.length === 0) {\n lines.push(\"- (none detected)\");\n } else {\n topUtilities.forEach(({ token, count }) => {\n lines.push(`- ${token}: ${count} occurrences`);\n });\n }\n\n if (topVariants.length > 0) {\n lines.push(\"\");\n lines.push(\"### Common Variants\");\n topVariants.forEach(({ token, count }) => {\n lines.push(`- ${token}: ${count} occurrences`);\n });\n }\n }\n\n // Tailwind theme tokens (optional)\n if (options.tailwindTheme) {\n const tt = options.tailwindTheme;\n lines.push(\"\");\n lines.push(\"### Tailwind Theme Tokens (from config)\");\n lines.push(`- configPath: ${tt.configPath}`);\n lines.push(`- colors: ${tt.colors.length}`);\n lines.push(`- spacingKeys: ${tt.spacingKeys.length}`);\n lines.push(`- borderRadiusKeys: ${tt.borderRadiusKeys.length}`);\n lines.push(`- fontFamilyKeys: ${tt.fontFamilyKeys.length}`);\n lines.push(`- fontSizeKeys: ${tt.fontSizeKeys.length}`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Truncates HTML to a maximum length\n */\nexport function truncateHTML(html: string, maxLength: number = 50000): string {\n if (html.length <= maxLength) return html;\n return html.slice(0, maxLength) + \"<!-- truncated -->\";\n}\n","/**\n * Style guide schema and utilities\n */\n\nimport type { StyleGuide, ColorRule, TypographyRule, SpacingRule, ComponentRule } from \"../types.js\";\n\n/**\n * Creates an empty style guide structure\n */\nexport function createEmptyStyleGuide(): StyleGuide {\n return {\n colors: [],\n typography: [],\n spacing: [],\n components: [],\n };\n}\n\n/**\n * Validates a style guide object\n */\nexport function validateStyleGuide(guide: unknown): guide is StyleGuide {\n if (!guide || typeof guide !== \"object\") return false;\n\n const g = guide as Record<string, unknown>;\n\n return (\n Array.isArray(g.colors) &&\n Array.isArray(g.typography) &&\n Array.isArray(g.spacing) &&\n Array.isArray(g.components)\n );\n}\n\n/**\n * Merges detected styles into an existing style guide\n */\nexport function mergeStyleGuides(\n existing: StyleGuide,\n detected: Partial<StyleGuide>\n): StyleGuide {\n return {\n colors: mergeColorRules(existing.colors, detected.colors || []),\n typography: mergeTypographyRules(existing.typography, detected.typography || []),\n spacing: mergeSpacingRules(existing.spacing, detected.spacing || []),\n components: mergeComponentRules(existing.components, detected.components || []),\n };\n}\n\nfunction mergeColorRules(existing: ColorRule[], detected: ColorRule[]): ColorRule[] {\n const merged = [...existing];\n detected.forEach((rule) => {\n const existingIndex = merged.findIndex(\n (e) => e.name === rule.name || e.value === rule.value\n );\n if (existingIndex === -1) {\n merged.push(rule);\n }\n });\n return merged;\n}\n\nfunction mergeTypographyRules(existing: TypographyRule[], detected: TypographyRule[]): TypographyRule[] {\n const merged = [...existing];\n detected.forEach((rule) => {\n const existingIndex = merged.findIndex((e) => e.element === rule.element);\n if (existingIndex === -1) {\n merged.push(rule);\n }\n });\n return merged;\n}\n\nfunction mergeSpacingRules(existing: SpacingRule[], detected: SpacingRule[]): SpacingRule[] {\n const merged = [...existing];\n detected.forEach((rule) => {\n const existingIndex = merged.findIndex(\n (e) => e.name === rule.name || e.value === rule.value\n );\n if (existingIndex === -1) {\n merged.push(rule);\n }\n });\n return merged;\n}\n\nfunction mergeComponentRules(existing: ComponentRule[], detected: ComponentRule[]): ComponentRule[] {\n const merged = [...existing];\n detected.forEach((rule) => {\n const existingIndex = merged.findIndex((e) => e.name === rule.name);\n if (existingIndex === -1) {\n merged.push(rule);\n }\n });\n return merged;\n}\n\n/**\n * Creates a color rule\n */\nexport function createColorRule(\n name: string,\n value: string,\n usage: string = \"\"\n): ColorRule {\n return { name, value: value.toUpperCase(), usage };\n}\n\n/**\n * Creates a typography rule\n */\nexport function createTypographyRule(\n element: string,\n options: Partial<Omit<TypographyRule, \"element\">> = {}\n): TypographyRule {\n return { element, ...options };\n}\n\n/**\n * Creates a spacing rule\n */\nexport function createSpacingRule(name: string, value: string): SpacingRule {\n return { name, value };\n}\n\n/**\n * Creates a component rule\n */\nexport function createComponentRule(name: string, styles: string[]): ComponentRule {\n return { name, styles };\n}\n\n","/**\n * Parse Markdown style guides into structured data\n */\n\nimport type {\n StyleGuide,\n ColorRule,\n TypographyRule,\n SpacingRule,\n ComponentRule,\n ExtractedStyleValues,\n} from \"../types.js\";\nimport { createEmptyStyleGuide } from \"./schema.js\";\n\n/**\n * Parses a Markdown style guide into a structured object\n */\nexport function parseStyleGuide(markdown: string): StyleGuide {\n const guide = createEmptyStyleGuide();\n const sections = splitIntoSections(markdown);\n\n sections.forEach(({ title, content }) => {\n const lowerTitle = title.toLowerCase();\n\n if (lowerTitle.includes(\"color\")) {\n guide.colors = parseColorSection(content);\n } else if (\n lowerTitle.includes(\"typography\") ||\n lowerTitle.includes(\"font\")\n ) {\n guide.typography = parseTypographySection(content);\n } else if (lowerTitle.includes(\"spacing\")) {\n guide.spacing = parseSpacingSection(content);\n } else if (lowerTitle.includes(\"component\")) {\n guide.components = parseComponentSection(content);\n }\n });\n\n return guide;\n}\n\ninterface Section {\n title: string;\n content: string;\n}\n\nfunction splitIntoSections(markdown: string): Section[] {\n const sections: Section[] = [];\n const lines = markdown.split(\"\\n\");\n\n let currentTitle = \"\";\n let currentContent: string[] = [];\n\n lines.forEach((line) => {\n const headerMatch = line.match(/^##\\s+(.+)$/);\n\n if (headerMatch) {\n if (currentTitle) {\n sections.push({\n title: currentTitle,\n content: currentContent.join(\"\\n\"),\n });\n }\n currentTitle = headerMatch[1];\n currentContent = [];\n } else {\n currentContent.push(line);\n }\n });\n\n if (currentTitle) {\n sections.push({\n title: currentTitle,\n content: currentContent.join(\"\\n\"),\n });\n }\n\n return sections;\n}\n\nfunction parseColorSection(content: string): ColorRule[] {\n const colors: ColorRule[] = [];\n const lines = content.split(\"\\n\");\n\n lines.forEach((line) => {\n // Match patterns like: - **Primary**: #3B82F6 (used in buttons)\n const match = line.match(\n /[-*]\\s*\\*?\\*?([^*:]+)\\*?\\*?:\\s*(#[A-Fa-f0-9]{6})\\s*(?:\\(([^)]+)\\))?/\n );\n\n if (match) {\n colors.push({\n name: match[1].trim(),\n value: match[2].toUpperCase(),\n usage: match[3] || \"\",\n });\n }\n });\n\n return colors;\n}\n\nfunction parseTypographySection(content: string): TypographyRule[] {\n const typography: TypographyRule[] = [];\n const lines = content.split(\"\\n\");\n\n lines.forEach((line) => {\n // Match patterns like: - **Headings**: font-family: \"Inter\", font-size: 24px\n const elementMatch = line.match(/[-*]\\s*\\*?\\*?([^*:]+)\\*?\\*?:\\s*(.+)/);\n\n if (elementMatch) {\n const rule: TypographyRule = {\n element: elementMatch[1].trim(),\n };\n\n const props = elementMatch[2];\n\n const fontFamilyMatch = props.match(/font-family:\\s*\"?([^\",]+)\"?/);\n if (fontFamilyMatch) rule.fontFamily = fontFamilyMatch[1].trim();\n\n const fontSizeMatch = props.match(/font-size:\\s*(\\d+px)/);\n if (fontSizeMatch) rule.fontSize = fontSizeMatch[1];\n\n const fontWeightMatch = props.match(/font-weight:\\s*(\\d+)/);\n if (fontWeightMatch) rule.fontWeight = fontWeightMatch[1];\n\n const lineHeightMatch = props.match(/line-height:\\s*([\\d.]+)/);\n if (lineHeightMatch) rule.lineHeight = lineHeightMatch[1];\n\n typography.push(rule);\n }\n });\n\n return typography;\n}\n\nfunction parseSpacingSection(content: string): SpacingRule[] {\n const spacing: SpacingRule[] = [];\n const lines = content.split(\"\\n\");\n\n lines.forEach((line) => {\n // Match patterns like: - **Base unit**: 4px\n const match = line.match(/[-*]\\s*\\*?\\*?([^*:]+)\\*?\\*?:\\s*(.+)/);\n\n if (match) {\n spacing.push({\n name: match[1].trim(),\n value: match[2].trim(),\n });\n }\n });\n\n return spacing;\n}\n\nfunction parseComponentSection(content: string): ComponentRule[] {\n const components: ComponentRule[] = [];\n const lines = content.split(\"\\n\");\n\n lines.forEach((line) => {\n // Match patterns like: - **Buttons**: rounded-lg, px-4 py-2\n const match = line.match(/[-*]\\s*\\*?\\*?([^*:]+)\\*?\\*?:\\s*(.+)/);\n\n if (match) {\n components.push({\n name: match[1].trim(),\n styles: match[2].split(\",\").map((s) => s.trim()),\n });\n }\n });\n\n return components;\n}\n\n/**\n * Parses sections from a Markdown style guide (simpler format)\n */\nexport function parseStyleGuideSections(\n content: string\n): Record<string, string> {\n const sections: Record<string, string> = {};\n const lines = content.split(\"\\n\");\n\n let currentSection = \"intro\";\n let currentContent: string[] = [];\n\n for (const line of lines) {\n const headerMatch = line.match(/^##\\s+(.+)$/);\n\n if (headerMatch) {\n if (currentContent.length > 0) {\n sections[currentSection.toLowerCase()] = currentContent\n .join(\"\\n\")\n .trim();\n }\n\n currentSection = headerMatch[1];\n currentContent = [];\n } else {\n currentContent.push(line);\n }\n }\n\n if (currentContent.length > 0) {\n sections[currentSection.toLowerCase()] = currentContent.join(\"\\n\").trim();\n }\n\n return sections;\n}\n\n/**\n * Extracts specific values from the style guide\n */\nexport function extractStyleValues(content: string): ExtractedStyleValues {\n const result: ExtractedStyleValues = {\n colors: [],\n fontSizes: [],\n fontFamilies: [],\n spacing: [],\n borderRadius: [],\n };\n\n // Extract hex colors\n const colorMatches = content.matchAll(/#[A-Fa-f0-9]{6}\\b/g);\n for (const match of colorMatches) {\n if (!result.colors.includes(match[0].toUpperCase())) {\n result.colors.push(match[0].toUpperCase());\n }\n }\n\n // Extract font sizes (e.g., 16px, 1.5rem)\n const fontSizeMatches = content.matchAll(/\\b(\\d+(?:\\.\\d+)?(?:px|rem|em))\\b/g);\n for (const match of fontSizeMatches) {\n if (!result.fontSizes.includes(match[1])) {\n result.fontSizes.push(match[1]);\n }\n }\n\n // Extract font families (quoted strings in font context)\n const fontFamilyMatches = content.matchAll(\n /font-family:\\s*[\"']?([^\"',\\n]+)/gi\n );\n for (const match of fontFamilyMatches) {\n const family = match[1].trim();\n if (!result.fontFamilies.includes(family)) {\n result.fontFamilies.push(family);\n }\n }\n\n return result;\n}\n\nexport interface TailwindAllowlist {\n allowAnyColor: boolean;\n allowStandardSpacing: boolean;\n allowedTailwindColors: Set<string>;\n allowedUtilities: Set<string>;\n allowedSpacingKeys: Set<string>;\n allowedBorderRadiusKeys: Set<string>;\n allowedFontSizeKeys: Set<string>;\n allowedFontFamilyKeys: Set<string>;\n}\n\n/**\n * Extract Tailwind / utility-class allowlist configuration from a style guide.\n *\n * Expected formats:\n * - A JSON code block inside a \"## Tailwind\" section (preferred; produced by UILint)\n * - Fallback: inline backticked utilities within the Tailwind section\n */\nexport function extractTailwindAllowlist(content: string): TailwindAllowlist {\n const empty: TailwindAllowlist = {\n allowAnyColor: false,\n allowStandardSpacing: false,\n allowedTailwindColors: new Set(),\n allowedUtilities: new Set(),\n allowedSpacingKeys: new Set(),\n allowedBorderRadiusKeys: new Set(),\n allowedFontSizeKeys: new Set(),\n allowedFontFamilyKeys: new Set(),\n };\n\n // Only look for allowlist details in the Tailwind section.\n const sections = parseStyleGuideSections(content);\n const tailwindSection =\n sections[\"tailwind\"] ??\n // defensive: some styleguides use different casing/spacing\n sections[\"tailwind utilities\"] ??\n \"\";\n\n if (!tailwindSection) return empty;\n\n const parsed = tryParseFirstJsonCodeBlock(tailwindSection);\n if (parsed && typeof parsed === \"object\") {\n const allowAnyColor = Boolean((parsed as any).allowAnyColor);\n const allowStandardSpacing = Boolean((parsed as any).allowStandardSpacing);\n\n const allowedUtilitiesArr = Array.isArray((parsed as any).allowedUtilities)\n ? ((parsed as any).allowedUtilities as unknown[]).filter(\n (u): u is string => typeof u === \"string\"\n )\n : [];\n\n const themeTokens = (parsed as any).themeTokens ?? {};\n const themeColors = Array.isArray(themeTokens.colors)\n ? (themeTokens.colors as unknown[]).filter(\n (c): c is string => typeof c === \"string\"\n )\n : [];\n const spacingKeys = Array.isArray(themeTokens.spacingKeys)\n ? (themeTokens.spacingKeys as unknown[]).filter(\n (k): k is string => typeof k === \"string\"\n )\n : [];\n const borderRadiusKeys = Array.isArray(themeTokens.borderRadiusKeys)\n ? (themeTokens.borderRadiusKeys as unknown[]).filter(\n (k): k is string => typeof k === \"string\"\n )\n : [];\n const fontFamilyKeys = Array.isArray(themeTokens.fontFamilyKeys)\n ? (themeTokens.fontFamilyKeys as unknown[]).filter(\n (k): k is string => typeof k === \"string\"\n )\n : [];\n const fontSizeKeys = Array.isArray(themeTokens.fontSizeKeys)\n ? (themeTokens.fontSizeKeys as unknown[]).filter(\n (k): k is string => typeof k === \"string\"\n )\n : [];\n\n const allowedTailwindColors = new Set<string>();\n for (const c of themeColors) {\n const raw = c.trim();\n if (!raw) continue;\n if (raw.toLowerCase().startsWith(\"tailwind:\")) {\n allowedTailwindColors.add(raw.toLowerCase());\n continue;\n }\n const m = raw.match(/^([a-zA-Z]+)-(\\d{2,3})$/);\n if (m) {\n allowedTailwindColors.add(`tailwind:${m[1].toLowerCase()}-${m[2]}`);\n }\n }\n\n return {\n allowAnyColor,\n allowStandardSpacing,\n allowedTailwindColors,\n allowedUtilities: new Set(\n allowedUtilitiesArr.map((s) => s.trim()).filter(Boolean)\n ),\n allowedSpacingKeys: new Set(\n spacingKeys.map((s) => s.trim()).filter(Boolean)\n ),\n allowedBorderRadiusKeys: new Set(\n borderRadiusKeys.map((s) => s.trim()).filter(Boolean)\n ),\n allowedFontSizeKeys: new Set(\n fontSizeKeys.map((s) => s.trim()).filter(Boolean)\n ),\n allowedFontFamilyKeys: new Set(\n fontFamilyKeys.map((s) => s.trim()).filter(Boolean)\n ),\n };\n }\n\n // Fallback: harvest backticked utilities from markdown.\n const backticked: string[] = [];\n for (const m of tailwindSection.matchAll(/`([^`]+)`/g)) {\n backticked.push(m[1]);\n }\n\n return {\n ...empty,\n allowedUtilities: new Set(\n backticked\n .flatMap((s) => s.split(/[,\\s]+/g))\n .map((s) => s.trim())\n .filter(Boolean)\n ),\n };\n}\n\nfunction tryParseFirstJsonCodeBlock(section: string): unknown | null {\n // Prefer ```json fenced blocks, but fall back to any fenced block.\n const jsonBlocks = [...section.matchAll(/```json\\s*([\\s\\S]*?)```/gi)];\n const anyBlocks = [...section.matchAll(/```\\s*([\\s\\S]*?)```/g)];\n\n const candidates = (jsonBlocks.length ? jsonBlocks : anyBlocks).map(\n (m) => m[1]\n );\n for (const raw of candidates) {\n const trimmed = raw.trim();\n if (!trimmed) continue;\n try {\n return JSON.parse(trimmed);\n } catch {\n continue;\n }\n }\n return null;\n}\n","/**\n * Generate Markdown style guides from extracted styles\n */\n\nimport type {\n ExtractedStyles,\n StyleGuide,\n TailwindThemeTokens,\n} from \"../types.js\";\nimport {\n extractClassTokensFromHtml,\n topEntries,\n} from \"../tailwind/class-tokens.js\";\n\n/**\n * Generates a Markdown style guide from extracted styles\n */\nexport interface GenerateStyleGuideOptions {\n /**\n * Optional HTML/TSX-ish string used to extract utility classes (Tailwind etc).\n */\n html?: string;\n /**\n * Optional Tailwind theme tokens (typically from tailwind.config.*).\n */\n tailwindTheme?: TailwindThemeTokens | null;\n}\n\nexport function generateStyleGuideFromStyles(\n styles: ExtractedStyles,\n options: GenerateStyleGuideOptions = {}\n): string {\n // NOTE: Style guide auto-generation has been removed.\n // UILint now requires an explicit, user-owned style guide file (typically\n // `.uilint/styleguide.md`) to avoid silently producing/overwriting rules.\n void styles;\n void options;\n throw new Error(\n 'Style guide auto-generation has been removed. Create \".uilint/styleguide.md\" at your workspace root (recommended: run \"/genstyleguide\" in Cursor).'\n );\n}\n\n/**\n * Finds the greatest common divisor of an array of numbers\n */\nfunction findGCD(numbers: number[]): number {\n if (numbers.length === 0) return 0;\n if (numbers.length === 1) return numbers[0];\n\n const gcd = (a: number, b: number): number => {\n a = Math.abs(Math.round(a));\n b = Math.abs(Math.round(b));\n while (b) {\n const t = b;\n b = a % b;\n a = t;\n }\n return a;\n };\n\n return numbers.reduce((acc, n) => gcd(acc, n));\n}\n\n/**\n * Converts a StyleGuide object back to Markdown\n */\nexport function styleGuideToMarkdown(guide: StyleGuide): string {\n const lines: string[] = [];\n\n lines.push(\"# UI Style Guide\");\n lines.push(\"\");\n\n // Colors\n lines.push(\"## Colors\");\n guide.colors.forEach((color) => {\n const usage = color.usage ? ` (${color.usage})` : \"\";\n lines.push(`- **${color.name}**: ${color.value}${usage}`);\n });\n lines.push(\"\");\n\n // Typography\n lines.push(\"## Typography\");\n guide.typography.forEach((typo) => {\n const props: string[] = [];\n if (typo.fontFamily) props.push(`font-family: \"${typo.fontFamily}\"`);\n if (typo.fontSize) props.push(`font-size: ${typo.fontSize}`);\n if (typo.fontWeight) props.push(`font-weight: ${typo.fontWeight}`);\n if (typo.lineHeight) props.push(`line-height: ${typo.lineHeight}`);\n lines.push(`- **${typo.element}**: ${props.join(\", \")}`);\n });\n lines.push(\"\");\n\n // Spacing\n lines.push(\"## Spacing\");\n guide.spacing.forEach((space) => {\n lines.push(`- **${space.name}**: ${space.value}`);\n });\n lines.push(\"\");\n\n // Components\n lines.push(\"## Components\");\n guide.components.forEach((comp) => {\n lines.push(`- **${comp.name}**: ${comp.styles.join(\", \")}`);\n });\n\n return lines.join(\"\\n\");\n}\n","/**\n * LLM prompt builders for UI consistency analysis\n */\n\nimport type {\n ElementSnapshot,\n GroupedSnapshot,\n ElementRole,\n} from \"./types.js\";\n\n/**\n * Formats an element for inclusion in the prompt (minimal data for LLM)\n */\nfunction formatElement(el: ElementSnapshot): string {\n const parts = [\n `id: ${el.id}`,\n `text: \"${el.text}\"`,\n el.component ? `component: ${el.component}` : null,\n `context: ${el.context || \"root\"}`,\n ].filter(Boolean);\n\n // Only include non-empty style values\n const styleEntries = Object.entries(el.styles).filter(\n ([, v]) => v && v !== \"0px\" && v !== \"none\" && v !== \"normal\"\n );\n if (styleEntries.length > 0) {\n const styleStr = styleEntries.map(([k, v]) => `${k}: ${v}`).join(\", \");\n parts.push(`styles: { ${styleStr} }`);\n }\n\n if (el.rect.width > 0 || el.rect.height > 0) {\n parts.push(`size: ${Math.round(el.rect.width)}x${Math.round(el.rect.height)}`);\n }\n\n return ` { ${parts.join(\", \")} }`;\n}\n\n/**\n * Formats a group of elements for the prompt\n */\nfunction formatGroup(\n groupName: string,\n elements: ElementSnapshot[]\n): string | null {\n if (elements.length < 2) return null;\n\n const lines = [`## ${groupName} (${elements.length} elements)`];\n elements.forEach((el) => {\n lines.push(formatElement(el));\n });\n return lines.join(\"\\n\");\n}\n\n/**\n * Builds a single prompt for analyzing ALL element groups\n * This reduces LLM calls to 1 for better performance\n */\nexport function buildConsistencyPrompt(snapshot: GroupedSnapshot): string {\n const groupSections: string[] = [];\n\n // Format each group that has 2+ elements\n const groups: Array<{ name: string; key: keyof GroupedSnapshot }> = [\n { name: \"Buttons\", key: \"buttons\" },\n { name: \"Headings\", key: \"headings\" },\n { name: \"Cards\", key: \"cards\" },\n { name: \"Links\", key: \"links\" },\n { name: \"Inputs\", key: \"inputs\" },\n { name: \"Containers\", key: \"containers\" },\n ];\n\n for (const { name, key } of groups) {\n const section = formatGroup(name, snapshot[key]);\n if (section) {\n groupSections.push(section);\n }\n }\n\n if (groupSections.length === 0) {\n return \"No element groups with 2+ elements found for consistency analysis.\";\n }\n\n return `You are a UI consistency analyzer. Your task is to find visual inconsistencies between similar UI elements that SHOULD match but DON'T.\n\n# Instructions\n\nAnalyze the following groups of UI elements. Within each group, elements should generally have consistent styling unless they're intentionally different (e.g., primary vs secondary buttons).\n\n## What to FLAG (violations):\n- Padding/spacing differences between similar elements (e.g., one button has 12px padding, another has 16px)\n- Font size or weight inconsistencies within same element types\n- Unintentional color variations (e.g., slightly different blues: #3B82F6 vs #3575E2)\n- Border radius mismatches (e.g., one card has 8px radius, another has 12px)\n- Shadow inconsistencies between similar components\n- Heading hierarchy issues (h1 should be larger than h2, h2 larger than h3)\n\n## What to NOT FLAG:\n- Intentional variations (primary vs secondary buttons, different heading levels)\n- Elements in different contexts that reasonably differ (header vs footer)\n- Sub-2px differences (likely rounding or subpixel rendering)\n- Different element types that shouldn't match\n\n# Element Groups\n\n${groupSections.join(\"\\n\\n\")}\n\n# Response Format\n\nRespond with JSON ONLY. Return a single JSON object with a \"violations\" array.\n\nEach violation should have:\n- elementIds: array of element IDs involved, e.g. [\"el-3\", \"el-7\"]\n- category: one of \"spacing\", \"color\", \"typography\", \"sizing\", \"borders\", \"shadows\"\n- severity: one of \"error\" (major inconsistency), \"warning\" (minor but noticeable), \"info\" (subtle)\n- message: short human-readable description\n- details: { property: the CSS property, values: array of differing values found, suggestion?: optional fix }\n\nExample response:\n{\n \"violations\": [\n {\n \"elementIds\": [\"el-3\", \"el-7\"],\n \"category\": \"spacing\",\n \"severity\": \"warning\",\n \"message\": \"Inconsistent padding on buttons\",\n \"details\": {\n \"property\": \"padding\",\n \"values\": [\"12px 24px\", \"16px 32px\"],\n \"suggestion\": \"Use consistent padding of 12px 24px\"\n }\n }\n ]\n}\n\nBe minimal. Only report significant inconsistencies. If no violations found, return {\"violations\": []}.`;\n}\n\n/**\n * Counts total elements across all groups\n */\nexport function countElements(snapshot: GroupedSnapshot): number {\n return (\n snapshot.buttons.length +\n snapshot.headings.length +\n snapshot.cards.length +\n snapshot.links.length +\n snapshot.inputs.length +\n snapshot.containers.length\n );\n}\n\n/**\n * Checks if a snapshot has any groups worth analyzing (2+ elements)\n */\nexport function hasAnalyzableGroups(snapshot: GroupedSnapshot): boolean {\n return (\n snapshot.buttons.length >= 2 ||\n snapshot.headings.length >= 2 ||\n snapshot.cards.length >= 2 ||\n snapshot.links.length >= 2 ||\n snapshot.inputs.length >= 2 ||\n snapshot.containers.length >= 2\n );\n}\n","/**\n * Consistency analysis logic - shared between CLI and API routes\n */\n\nimport type {\n GroupedSnapshot,\n Violation,\n ConsistencyResult,\n ViolationCategory,\n ViolationSeverity,\n} from \"./types.js\";\nimport {\n buildConsistencyPrompt,\n countElements,\n hasAnalyzableGroups,\n} from \"./prompts.js\";\nimport { OllamaClient } from \"../ollama/client.js\";\n\nconst VALID_CATEGORIES: ViolationCategory[] = [\n \"spacing\",\n \"color\",\n \"typography\",\n \"sizing\",\n \"borders\",\n \"shadows\",\n];\n\nconst VALID_SEVERITIES: ViolationSeverity[] = [\"error\", \"warning\", \"info\"];\n\n/**\n * Parses and validates a GroupedSnapshot from JSON string\n */\nexport function parseGroupedSnapshot(json: string): GroupedSnapshot | null {\n try {\n const parsed = JSON.parse(json);\n\n // Validate structure\n if (!parsed || typeof parsed !== \"object\") return null;\n\n const result: GroupedSnapshot = {\n buttons: Array.isArray(parsed.buttons) ? parsed.buttons : [],\n headings: Array.isArray(parsed.headings) ? parsed.headings : [],\n cards: Array.isArray(parsed.cards) ? parsed.cards : [],\n links: Array.isArray(parsed.links) ? parsed.links : [],\n inputs: Array.isArray(parsed.inputs) ? parsed.inputs : [],\n containers: Array.isArray(parsed.containers) ? parsed.containers : [],\n };\n\n return result;\n } catch {\n return null;\n }\n}\n\n/**\n * Parses violations from LLM response with defensive handling\n */\nexport function parseViolationsResponse(response: string): Violation[] {\n try {\n // Try direct JSON parse first\n const parsed = JSON.parse(response);\n if (Array.isArray(parsed.violations)) {\n return validateViolations(parsed.violations);\n }\n return [];\n } catch {\n // Try to extract JSON from response using regex\n const jsonMatch = response.match(/\\{[\\s\\S]*\"violations\"[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const parsed = JSON.parse(jsonMatch[0]);\n if (Array.isArray(parsed.violations)) {\n return validateViolations(parsed.violations);\n }\n } catch {\n // Fallback failed\n }\n }\n return [];\n }\n}\n\n/**\n * Validates and filters violations to ensure correct structure\n */\nexport function validateViolations(violations: unknown[]): Violation[] {\n return violations\n .filter((v): v is Violation => {\n if (!v || typeof v !== \"object\") return false;\n const obj = v as Record<string, unknown>;\n\n // Required fields\n if (!Array.isArray(obj.elementIds)) return false;\n if (typeof obj.category !== \"string\") return false;\n if (typeof obj.severity !== \"string\") return false;\n if (typeof obj.message !== \"string\") return false;\n if (!obj.details || typeof obj.details !== \"object\") return false;\n\n // Validate category\n if (!VALID_CATEGORIES.includes(obj.category as ViolationCategory))\n return false;\n\n // Validate severity\n if (!VALID_SEVERITIES.includes(obj.severity as ViolationSeverity))\n return false;\n\n return true;\n })\n .map((v) => ({\n elementIds: v.elementIds,\n category: v.category,\n severity: v.severity,\n message: v.message,\n details: {\n property:\n typeof v.details.property === \"string\" ? v.details.property : \"\",\n values: Array.isArray(v.details.values) ? v.details.values : [],\n suggestion:\n typeof v.details.suggestion === \"string\"\n ? v.details.suggestion\n : undefined,\n },\n }));\n}\n\nexport interface AnalyzeConsistencyOptions {\n /** Ollama model to use */\n model?: string;\n /** Ollama base URL */\n baseUrl?: string;\n}\n\n/**\n * Analyzes a grouped snapshot for UI consistency violations\n * This is the main entry point for consistency analysis\n */\nexport async function analyzeConsistency(\n snapshot: GroupedSnapshot,\n options: AnalyzeConsistencyOptions = {}\n): Promise<ConsistencyResult> {\n const startTime = Date.now();\n const elementCount = countElements(snapshot);\n\n // Check if there are analyzable groups\n if (!hasAnalyzableGroups(snapshot)) {\n return {\n violations: [],\n elementCount,\n analysisTime: Date.now() - startTime,\n };\n }\n\n // Build prompt and call LLM\n const prompt = buildConsistencyPrompt(snapshot);\n const client = new OllamaClient({\n model: options.model,\n baseUrl: options.baseUrl,\n });\n\n const response = await client.complete(prompt, { json: true });\n\n // Parse violations\n const violations = parseViolationsResponse(response);\n\n return {\n violations,\n elementCount,\n analysisTime: Date.now() - startTime,\n };\n}\n\n/**\n * Formats violations for plain text output\n */\nexport function formatConsistencyViolations(violations: Violation[]): string {\n if (violations.length === 0) {\n return \"✓ No consistency issues found.\";\n }\n\n const lines: string[] = [\n `Found ${violations.length} consistency issue(s):\\n`,\n ];\n\n const severityIcons: Record<ViolationSeverity, string> = {\n error: \"✖\",\n warning: \"⚠\",\n info: \"ℹ\",\n };\n\n violations.forEach((v, i) => {\n const icon = severityIcons[v.severity] || \"•\";\n\n lines.push(`${i + 1}. ${icon} [${v.category}] ${v.message}`);\n lines.push(` Elements: ${v.elementIds.join(\", \")}`);\n\n if (v.details.property) {\n lines.push(` Property: ${v.details.property}`);\n }\n if (v.details.values.length > 0) {\n lines.push(` Values: ${v.details.values.join(\" vs \")}`);\n }\n if (v.details.suggestion) {\n lines.push(` Suggestion: ${v.details.suggestion}`);\n }\n lines.push(\"\");\n });\n\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -64,10 +64,61 @@ interface AnalysisResult {
|
|
|
64
64
|
suggestedStyleGuide?: string;
|
|
65
65
|
analysisTime: number;
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Lightweight issue type used for per-source scanning (e.g. via data-loc).
|
|
69
|
+
* This intentionally differs from `UILintIssue` (which is for styleSummary analysis).
|
|
70
|
+
*/
|
|
71
|
+
interface UILintScanIssue {
|
|
72
|
+
/** Line number in the source file (1-based). */
|
|
73
|
+
line?: number;
|
|
74
|
+
/** Human-readable description of the issue. */
|
|
75
|
+
message: string;
|
|
76
|
+
/** Optional data-loc reference (format: "path:line:column") */
|
|
77
|
+
dataLoc?: string;
|
|
78
|
+
}
|
|
79
|
+
interface UILintSourceScanResult {
|
|
80
|
+
issues: UILintScanIssue[];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Represents an active generation span that can be ended with output data.
|
|
84
|
+
* Returned by onGenerationStart when instrumentation is enabled.
|
|
85
|
+
*/
|
|
86
|
+
interface InstrumentationSpan {
|
|
87
|
+
/** End the span with the generation output and optional usage metrics */
|
|
88
|
+
end: (output: string, usage?: {
|
|
89
|
+
promptTokens?: number;
|
|
90
|
+
completionTokens?: number;
|
|
91
|
+
totalTokens?: number;
|
|
92
|
+
error?: string;
|
|
93
|
+
}) => void;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Optional instrumentation callbacks for LLM observability.
|
|
97
|
+
* Allows plugging in any observability tool (Langfuse, OpenTelemetry, etc.)
|
|
98
|
+
* without adding hard dependencies to uilint-core.
|
|
99
|
+
*/
|
|
100
|
+
interface LLMInstrumentationCallbacks {
|
|
101
|
+
/**
|
|
102
|
+
* Called when an LLM generation starts.
|
|
103
|
+
* Return an InstrumentationSpan to track the generation, or void to skip.
|
|
104
|
+
*/
|
|
105
|
+
onGenerationStart?: (params: {
|
|
106
|
+
/** Operation name (e.g., "ollama-generate", "analyze-styles") */
|
|
107
|
+
name: string;
|
|
108
|
+
/** Model identifier */
|
|
109
|
+
model: string;
|
|
110
|
+
/** The prompt sent to the LLM */
|
|
111
|
+
prompt: string;
|
|
112
|
+
/** Optional metadata */
|
|
113
|
+
metadata?: Record<string, unknown>;
|
|
114
|
+
}) => InstrumentationSpan | void;
|
|
115
|
+
}
|
|
67
116
|
interface OllamaClientOptions {
|
|
68
117
|
baseUrl?: string;
|
|
69
118
|
model?: string;
|
|
70
119
|
timeout?: number;
|
|
120
|
+
/** Optional instrumentation callbacks for observability (Langfuse, OpenTelemetry, etc.) */
|
|
121
|
+
instrumentation?: LLMInstrumentationCallbacks;
|
|
71
122
|
}
|
|
72
123
|
type StreamProgressCallback = (latestLine: string, fullResponse: string) => void;
|
|
73
124
|
interface ExtractedStyleValues {
|
|
@@ -113,6 +164,34 @@ interface BuildSourceAnalysisPromptOptions {
|
|
|
113
164
|
* without attempting to parse it as HTML/DOM.
|
|
114
165
|
*/
|
|
115
166
|
declare function buildSourceAnalysisPrompt(source: string, styleGuide: string | null, options?: BuildSourceAnalysisPromptOptions): string;
|
|
167
|
+
interface BuildSourceScanPromptOptions {
|
|
168
|
+
/**
|
|
169
|
+
* Display filename/path for context in the prompt.
|
|
170
|
+
*/
|
|
171
|
+
filePath?: string;
|
|
172
|
+
/**
|
|
173
|
+
* Optional focus target for manual scan UX.
|
|
174
|
+
*/
|
|
175
|
+
componentName?: string;
|
|
176
|
+
componentLine?: number;
|
|
177
|
+
/**
|
|
178
|
+
* If true, the scan is for the selected element + children (UI text only).
|
|
179
|
+
*/
|
|
180
|
+
includeChildren?: boolean;
|
|
181
|
+
/**
|
|
182
|
+
* Optional list of data-loc values (format: "path:line:column") that the model MUST
|
|
183
|
+
* restrict issues to (so UI can highlight exactly).
|
|
184
|
+
*/
|
|
185
|
+
dataLocs?: string[];
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Builds a prompt for scanning a source file/snippet for issues that can be mapped
|
|
189
|
+
* back to rendered DOM elements via `data-loc`.
|
|
190
|
+
*
|
|
191
|
+
* Response schema (JSON):
|
|
192
|
+
* { "issues": [{ "line"?: number, "message": string, "dataLoc"?: string }] }
|
|
193
|
+
*/
|
|
194
|
+
declare function buildSourceScanPrompt(sourceCode: string, styleGuide: string | null, options?: BuildSourceScanPromptOptions): string;
|
|
116
195
|
/**
|
|
117
196
|
* Builds a prompt for style guide generation
|
|
118
197
|
*/
|
|
@@ -126,6 +205,7 @@ declare class OllamaClient {
|
|
|
126
205
|
private baseUrl;
|
|
127
206
|
private model;
|
|
128
207
|
private timeout;
|
|
208
|
+
private instrumentation?;
|
|
129
209
|
constructor(options?: OllamaClientOptions);
|
|
130
210
|
/**
|
|
131
211
|
* Low-level completion API for custom prompts (used by installers/tools).
|
|
@@ -175,6 +255,15 @@ declare class OllamaClient {
|
|
|
175
255
|
* Sets the model
|
|
176
256
|
*/
|
|
177
257
|
setModel(model: string): void;
|
|
258
|
+
/**
|
|
259
|
+
* Sets instrumentation callbacks for observability.
|
|
260
|
+
* This allows configuring instrumentation after construction.
|
|
261
|
+
*/
|
|
262
|
+
setInstrumentation(instrumentation: LLMInstrumentationCallbacks | undefined): void;
|
|
263
|
+
/**
|
|
264
|
+
* Returns true if instrumentation is currently configured.
|
|
265
|
+
*/
|
|
266
|
+
hasInstrumentation(): boolean;
|
|
178
267
|
}
|
|
179
268
|
declare function getOllamaClient(options?: OllamaClientOptions): OllamaClient;
|
|
180
269
|
|
|
@@ -494,4 +583,4 @@ declare function analyzeConsistency(snapshot: GroupedSnapshot, options?: Analyze
|
|
|
494
583
|
*/
|
|
495
584
|
declare function formatConsistencyViolations(violations: Violation[]): string;
|
|
496
585
|
|
|
497
|
-
export { type AnalysisResult, type AnalyzeConsistencyOptions, type ColorRule, type ComponentRule, type ConsistencyResult, type DOMSnapshot, type ElementRole, type ElementSnapshot, type ExtractedStyleValues, type ExtractedStyles, type GroupedSnapshot, OllamaClient, type OllamaClientOptions, type SerializedStyles, type SpacingRule, type StreamProgressCallback, type StyleGuide, type StyleSnapshot, type TailwindThemeTokens, type TypographyRule, UILINT_DEFAULT_OLLAMA_MODEL, type UILintIssue, type Violation, type ViolationCategory, type ViolationSeverity, analyzeConsistency, buildAnalysisPrompt, buildConsistencyPrompt, buildSourceAnalysisPrompt, buildStyleGuidePrompt, countElements, createColorRule, createComponentRule, createEmptyStyleGuide, createSpacingRule, createStyleSummary, createTypographyRule, deserializeStyles, extractStyleValues, extractStyles, extractStylesFromDOM, extractTailwindAllowlist, formatConsistencyViolations, formatViolationsText, generateStyleGuideFromStyles, getOllamaClient, hasAnalyzableGroups, mergeStyleGuides, parseGroupedSnapshot, parseStyleGuide, parseStyleGuideSections, parseViolationsResponse, sanitizeIssues, serializeStyles, styleGuideToMarkdown, truncateHTML, validateStyleGuide, validateViolations };
|
|
586
|
+
export { type AnalysisResult, type AnalyzeConsistencyOptions, type ColorRule, type ComponentRule, type ConsistencyResult, type DOMSnapshot, type ElementRole, type ElementSnapshot, type ExtractedStyleValues, type ExtractedStyles, type GroupedSnapshot, type InstrumentationSpan, type LLMInstrumentationCallbacks, OllamaClient, type OllamaClientOptions, type SerializedStyles, type SpacingRule, type StreamProgressCallback, type StyleGuide, type StyleSnapshot, type TailwindThemeTokens, type TypographyRule, UILINT_DEFAULT_OLLAMA_MODEL, type UILintIssue, type UILintScanIssue, type UILintSourceScanResult, type Violation, type ViolationCategory, type ViolationSeverity, analyzeConsistency, buildAnalysisPrompt, buildConsistencyPrompt, buildSourceAnalysisPrompt, buildSourceScanPrompt, buildStyleGuidePrompt, countElements, createColorRule, createComponentRule, createEmptyStyleGuide, createSpacingRule, createStyleSummary, createTypographyRule, deserializeStyles, extractStyleValues, extractStyles, extractStylesFromDOM, extractTailwindAllowlist, formatConsistencyViolations, formatViolationsText, generateStyleGuideFromStyles, getOllamaClient, hasAnalyzableGroups, mergeStyleGuides, parseGroupedSnapshot, parseStyleGuide, parseStyleGuideSections, parseViolationsResponse, sanitizeIssues, serializeStyles, styleGuideToMarkdown, truncateHTML, validateStyleGuide, validateViolations };
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
buildAnalysisPrompt,
|
|
6
6
|
buildConsistencyPrompt,
|
|
7
7
|
buildSourceAnalysisPrompt,
|
|
8
|
+
buildSourceScanPrompt,
|
|
8
9
|
buildStyleGuidePrompt,
|
|
9
10
|
countElements,
|
|
10
11
|
createColorRule,
|
|
@@ -34,7 +35,7 @@ import {
|
|
|
34
35
|
truncateHTML,
|
|
35
36
|
validateStyleGuide,
|
|
36
37
|
validateViolations
|
|
37
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-FIESH4VM.js";
|
|
38
39
|
export {
|
|
39
40
|
OllamaClient,
|
|
40
41
|
UILINT_DEFAULT_OLLAMA_MODEL,
|
|
@@ -42,6 +43,7 @@ export {
|
|
|
42
43
|
buildAnalysisPrompt,
|
|
43
44
|
buildConsistencyPrompt,
|
|
44
45
|
buildSourceAnalysisPrompt,
|
|
46
|
+
buildSourceScanPrompt,
|
|
45
47
|
buildStyleGuidePrompt,
|
|
46
48
|
countElements,
|
|
47
49
|
createColorRule,
|
package/dist/node.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DOMSnapshot, TailwindThemeTokens } from './index.js';
|
|
2
|
-
export { AnalysisResult, AnalyzeConsistencyOptions, ColorRule, ComponentRule, ConsistencyResult, ElementRole, ElementSnapshot, ExtractedStyleValues, ExtractedStyles, GroupedSnapshot, OllamaClient, OllamaClientOptions, SerializedStyles, SpacingRule, StreamProgressCallback, StyleGuide, StyleSnapshot, TypographyRule, UILINT_DEFAULT_OLLAMA_MODEL, UILintIssue, Violation, ViolationCategory, ViolationSeverity, analyzeConsistency, buildAnalysisPrompt, buildConsistencyPrompt, buildSourceAnalysisPrompt, buildStyleGuidePrompt, countElements, createColorRule, createComponentRule, createEmptyStyleGuide, createSpacingRule, createStyleSummary, createTypographyRule, deserializeStyles, extractStyleValues, extractStyles, extractStylesFromDOM, extractTailwindAllowlist, formatConsistencyViolations, formatViolationsText, generateStyleGuideFromStyles, getOllamaClient, hasAnalyzableGroups, mergeStyleGuides, parseGroupedSnapshot, parseStyleGuide, parseStyleGuideSections, parseViolationsResponse, sanitizeIssues, serializeStyles, styleGuideToMarkdown, truncateHTML, validateStyleGuide, validateViolations } from './index.js';
|
|
2
|
+
export { AnalysisResult, AnalyzeConsistencyOptions, ColorRule, ComponentRule, ConsistencyResult, ElementRole, ElementSnapshot, ExtractedStyleValues, ExtractedStyles, GroupedSnapshot, InstrumentationSpan, LLMInstrumentationCallbacks, OllamaClient, OllamaClientOptions, SerializedStyles, SpacingRule, StreamProgressCallback, StyleGuide, StyleSnapshot, TypographyRule, UILINT_DEFAULT_OLLAMA_MODEL, UILintIssue, UILintScanIssue, UILintSourceScanResult, Violation, ViolationCategory, ViolationSeverity, analyzeConsistency, buildAnalysisPrompt, buildConsistencyPrompt, buildSourceAnalysisPrompt, buildSourceScanPrompt, buildStyleGuidePrompt, countElements, createColorRule, createComponentRule, createEmptyStyleGuide, createSpacingRule, createStyleSummary, createTypographyRule, deserializeStyles, extractStyleValues, extractStyles, extractStylesFromDOM, extractTailwindAllowlist, formatConsistencyViolations, formatViolationsText, generateStyleGuideFromStyles, getOllamaClient, hasAnalyzableGroups, mergeStyleGuides, parseGroupedSnapshot, parseStyleGuide, parseStyleGuideSections, parseViolationsResponse, sanitizeIssues, serializeStyles, styleGuideToMarkdown, truncateHTML, validateStyleGuide, validateViolations } from './index.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Ollama bootstrapping utilities (Node.js only).
|
|
@@ -65,6 +65,13 @@ declare function hasStdin(): boolean;
|
|
|
65
65
|
* Style guide file operations
|
|
66
66
|
*/
|
|
67
67
|
declare const STYLEGUIDE_PATHS: string[];
|
|
68
|
+
/**
|
|
69
|
+
* Walk upward from a starting directory and look specifically for `.uilint/styleguide.md`.
|
|
70
|
+
*
|
|
71
|
+
* This is intended for flows where the "project root" is ambiguous (e.g., analyzing
|
|
72
|
+
* an arbitrary file path) and we want the nearest `.uilint` config on the way up.
|
|
73
|
+
*/
|
|
74
|
+
declare function findUILintStyleGuideUpwards(startDir: string): string | null;
|
|
68
75
|
/**
|
|
69
76
|
* Finds the style guide file in a project
|
|
70
77
|
*/
|
|
@@ -112,4 +119,4 @@ declare function findWorkspaceRoot(startDir: string): string;
|
|
|
112
119
|
declare function findTailwindConfigPath(startDir: string): string | null;
|
|
113
120
|
declare function readTailwindThemeTokens(projectDir: string): TailwindThemeTokens | null;
|
|
114
121
|
|
|
115
|
-
export { DOMSnapshot, STYLEGUIDE_PATHS, TailwindThemeTokens, ensureOllamaInstalledOrExplain, ensureOllamaModelPulled, ensureOllamaReady, ensureOllamaRunning, findStyleGuidePath, findTailwindConfigPath, findWorkspaceRoot, getDefaultStyleGuidePath, hasStdin, isJSON, isOllamaInstalled, parseCLIInput, parseHTML, readStdin, readStyleGuide, readStyleGuideFromProject, readTailwindThemeTokens, styleGuideExists, writeStyleGuide };
|
|
122
|
+
export { DOMSnapshot, STYLEGUIDE_PATHS, TailwindThemeTokens, ensureOllamaInstalledOrExplain, ensureOllamaModelPulled, ensureOllamaReady, ensureOllamaRunning, findStyleGuidePath, findTailwindConfigPath, findUILintStyleGuideUpwards, findWorkspaceRoot, getDefaultStyleGuidePath, hasStdin, isJSON, isOllamaInstalled, parseCLIInput, parseHTML, readStdin, readStyleGuide, readStyleGuideFromProject, readTailwindThemeTokens, styleGuideExists, writeStyleGuide };
|
package/dist/node.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
buildAnalysisPrompt,
|
|
6
6
|
buildConsistencyPrompt,
|
|
7
7
|
buildSourceAnalysisPrompt,
|
|
8
|
+
buildSourceScanPrompt,
|
|
8
9
|
buildStyleGuidePrompt,
|
|
9
10
|
countElements,
|
|
10
11
|
createColorRule,
|
|
@@ -34,7 +35,7 @@ import {
|
|
|
34
35
|
truncateHTML,
|
|
35
36
|
validateStyleGuide,
|
|
36
37
|
validateViolations
|
|
37
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-FIESH4VM.js";
|
|
38
39
|
|
|
39
40
|
// src/ollama/bootstrap.ts
|
|
40
41
|
import { spawn, spawnSync } from "child_process";
|
|
@@ -265,6 +266,16 @@ var STYLEGUIDE_PATHS = [
|
|
|
265
266
|
"styleguide.md",
|
|
266
267
|
".uilint/style-guide.md"
|
|
267
268
|
];
|
|
269
|
+
function findUILintStyleGuideUpwards(startDir) {
|
|
270
|
+
let dir = startDir;
|
|
271
|
+
for (; ; ) {
|
|
272
|
+
const candidate = join(dir, ".uilint", "styleguide.md");
|
|
273
|
+
if (existsSync(candidate)) return candidate;
|
|
274
|
+
const parent = dirname(dir);
|
|
275
|
+
if (parent === dir) return null;
|
|
276
|
+
dir = parent;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
268
279
|
function findStyleGuidePath(projectPath) {
|
|
269
280
|
for (const relativePath of STYLEGUIDE_PATHS) {
|
|
270
281
|
const fullPath = join(projectPath, relativePath);
|
|
@@ -428,6 +439,7 @@ export {
|
|
|
428
439
|
buildAnalysisPrompt,
|
|
429
440
|
buildConsistencyPrompt,
|
|
430
441
|
buildSourceAnalysisPrompt,
|
|
442
|
+
buildSourceScanPrompt,
|
|
431
443
|
buildStyleGuidePrompt,
|
|
432
444
|
countElements,
|
|
433
445
|
createColorRule,
|
|
@@ -447,6 +459,7 @@ export {
|
|
|
447
459
|
extractTailwindAllowlist,
|
|
448
460
|
findStyleGuidePath,
|
|
449
461
|
findTailwindConfigPath,
|
|
462
|
+
findUILintStyleGuideUpwards,
|
|
450
463
|
findWorkspaceRoot,
|
|
451
464
|
formatConsistencyViolations,
|
|
452
465
|
formatViolationsText,
|
package/dist/node.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ollama/bootstrap.ts","../src/scanner/html-parser.ts","../src/styleguide/reader.ts","../src/utils/workspace-root.ts","../src/tailwind/config-reader.ts"],"sourcesContent":["/**\n * Ollama bootstrapping utilities (Node.js only).\n *\n * Goals:\n * - Detect whether Ollama is installed.\n * - Ensure the Ollama daemon is running (best effort: start `ollama serve` detached).\n * - Ensure a given model is pulled (best effort: run `ollama pull <model>`).\n */\n\nimport { spawn, spawnSync } from \"child_process\";\nimport readline from \"readline\";\nimport { OllamaClient } from \"./client.js\";\nimport { UILINT_DEFAULT_OLLAMA_MODEL } from \"./defaults.js\";\n\nconst DEFAULT_OLLAMA_BASE_URL = \"http://localhost:11434\";\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction isInteractiveTTY(): boolean {\n return Boolean(process.stdin.isTTY && process.stdout.isTTY);\n}\n\nasync function promptYesNo(question: string): Promise<boolean> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n return await new Promise<boolean>((resolve) => {\n rl.question(`${question} (y/N) `, (answer) => {\n rl.close();\n const normalized = (answer || \"\").trim().toLowerCase();\n resolve(normalized === \"y\" || normalized === \"yes\");\n });\n });\n}\n\nexport function isOllamaInstalled(): boolean {\n const result = spawnSync(\"ollama\", [\"--version\"], { stdio: \"ignore\" });\n if (\n result.error &&\n (result.error as NodeJS.ErrnoException).code === \"ENOENT\"\n ) {\n return false;\n }\n return result.status === 0;\n}\n\nfunction isBrewInstalled(): boolean {\n const result = spawnSync(\"brew\", [\"--version\"], { stdio: \"ignore\" });\n if (\n result.error &&\n (result.error as NodeJS.ErrnoException).code === \"ENOENT\"\n ) {\n return false;\n }\n return result.status === 0;\n}\n\nfunction getInstallInstructions(): string[] {\n const lines: string[] = [\n \"Ollama is required for LLM-backed analysis.\",\n \"\",\n \"Install Ollama:\",\n \" - Download: https://ollama.ai\",\n ];\n\n if (process.platform === \"darwin\") {\n lines.push(\" - Homebrew: brew install ollama\");\n }\n\n lines.push(\"\");\n lines.push(\"Then start it:\");\n lines.push(\" ollama serve\");\n return lines;\n}\n\nasync function maybeInstallOllamaWithBrew(): Promise<boolean> {\n if (process.platform !== \"darwin\") return false;\n if (!isInteractiveTTY()) return false;\n\n if (!isBrewInstalled()) {\n // We can't auto-install without brew; leave instructions to the caller.\n return false;\n }\n\n const ok = await promptYesNo(\n \"Ollama is not installed. Install with Homebrew now?\"\n );\n if (!ok) return false;\n\n await new Promise<void>((resolve, reject) => {\n const child = spawn(\"brew\", [\"install\", \"ollama\"], { stdio: \"inherit\" });\n child.on(\"error\", reject);\n child.on(\"exit\", (code) => {\n if (code === 0) resolve();\n else reject(new Error(`brew install ollama failed (exit ${code})`));\n });\n });\n\n return isOllamaInstalled();\n}\n\nexport async function ensureOllamaInstalledOrExplain(): Promise<void> {\n if (isOllamaInstalled()) return;\n\n const installedViaBrew = await maybeInstallOllamaWithBrew();\n if (installedViaBrew) return;\n\n const extra: string[] = [];\n if (process.platform === \"darwin\" && !isBrewInstalled()) {\n extra.push(\"\");\n extra.push(\"Homebrew is not installed. Install it first, then run:\");\n extra.push(\" brew install ollama\");\n }\n\n throw new Error([...getInstallInstructions(), ...extra].join(\"\\n\"));\n}\n\nexport async function ensureOllamaRunning(options?: {\n timeoutMs?: number;\n baseUrl?: string;\n}): Promise<void> {\n await ensureOllamaInstalledOrExplain();\n\n const baseUrl = options?.baseUrl || DEFAULT_OLLAMA_BASE_URL;\n const client = new OllamaClient({ baseUrl });\n const timeoutMs = options?.timeoutMs ?? 10_000;\n\n if (await client.isAvailable()) return;\n\n // Best-effort background start. We do not stop it later.\n try {\n const child = spawn(\"ollama\", [\"serve\"], {\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n } catch (err) {\n throw new Error(\n `Failed to start Ollama automatically.\\n\\n${getInstallInstructions().join(\n \"\\n\"\n )}\\n\\nDetails: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n if (await client.isAvailable()) return;\n await sleep(250);\n }\n\n throw new Error(\n [\n \"Ollama did not become ready in time.\",\n \"\",\n \"Try starting it manually:\",\n \" ollama serve\",\n ].join(\"\\n\")\n );\n}\n\nasync function fetchOllamaTags(baseUrl: string): Promise<string[]> {\n const res = await fetch(`${baseUrl}/api/tags`, {\n method: \"GET\",\n signal: AbortSignal.timeout(5000),\n });\n\n if (!res.ok) {\n throw new Error(`Ollama tags endpoint returned ${res.status}`);\n }\n\n const data = (await res.json()) as { models?: Array<{ name?: string }> };\n const names = (data.models ?? [])\n .map((m) => m.name)\n .filter((n): n is string => typeof n === \"string\");\n return names;\n}\n\nexport async function ensureOllamaModelPulled(options?: {\n model?: string;\n baseUrl?: string;\n}): Promise<void> {\n const desired = (options?.model || UILINT_DEFAULT_OLLAMA_MODEL).trim();\n if (!desired) return;\n\n const baseUrl = options?.baseUrl || DEFAULT_OLLAMA_BASE_URL;\n const tags = await fetchOllamaTags(baseUrl);\n if (tags.includes(desired)) return;\n\n await new Promise<void>((resolve, reject) => {\n const child = spawn(\"ollama\", [\"pull\", desired], { stdio: \"inherit\" });\n child.on(\"error\", reject);\n child.on(\"exit\", (code) => {\n if (code === 0) resolve();\n else reject(new Error(`ollama pull ${desired} failed (exit ${code})`));\n });\n });\n\n const tagsAfter = await fetchOllamaTags(baseUrl);\n if (!tagsAfter.includes(desired)) {\n throw new Error(\n `Model ${desired} did not appear in Ollama tags after pulling.`\n );\n }\n}\n\nexport async function ensureOllamaReady(options?: {\n model?: string;\n timeoutMs?: number;\n baseUrl?: string;\n}): Promise<void> {\n await ensureOllamaRunning({\n timeoutMs: options?.timeoutMs,\n baseUrl: options?.baseUrl,\n });\n await ensureOllamaModelPulled({\n model: options?.model,\n baseUrl: options?.baseUrl,\n });\n}\n","/**\n * HTML parser for extracting styles using JSDOM\n * Used by CLI and Node.js environments\n */\n\nimport { JSDOM } from \"jsdom\";\nimport type { ExtractedStyles, DOMSnapshot, SerializedStyles } from \"../types.js\";\nimport { extractStyles, deserializeStyles, truncateHTML } from \"./style-extractor.js\";\n\nexport interface ParseOptions {\n /**\n * If true, tries to load and process linked stylesheets\n */\n loadStylesheets?: boolean;\n /**\n * Max length for HTML in snapshot\n */\n maxHtmlLength?: number;\n}\n\n/**\n * Parses raw HTML and extracts styles using JSDOM\n */\nexport function parseHTML(\n html: string,\n options: ParseOptions = {}\n): DOMSnapshot {\n const { maxHtmlLength = 50000 } = options;\n\n const dom = new JSDOM(html, {\n runScripts: \"outside-only\",\n pretendToBeVisual: true,\n });\n\n const { document, getComputedStyle } = dom.window;\n const root = document.body || document.documentElement;\n\n const styles = extractStyles(root, getComputedStyle);\n const elementCount = root.querySelectorAll(\"*\").length;\n\n return {\n html: truncateHTML(html, maxHtmlLength),\n styles,\n elementCount,\n timestamp: Date.now(),\n };\n}\n\n/**\n * Input format for CLI - can be raw HTML or pre-extracted JSON\n */\nexport interface CLIInput {\n html: string;\n styles?: SerializedStyles;\n}\n\n/**\n * Parses CLI input which can be either raw HTML or JSON with pre-extracted styles\n */\nexport function parseCLIInput(input: string): DOMSnapshot {\n // Try to parse as JSON first\n try {\n const parsed = JSON.parse(input) as CLIInput;\n \n if (parsed.html && parsed.styles) {\n // Pre-extracted styles from browser/test environment\n return {\n html: truncateHTML(parsed.html),\n styles: deserializeStyles(parsed.styles),\n elementCount: 0, // Not available for pre-extracted\n timestamp: Date.now(),\n };\n } else if (parsed.html) {\n // Just HTML in JSON format\n return parseHTML(parsed.html);\n }\n } catch {\n // Not JSON, treat as raw HTML\n }\n\n // Parse as raw HTML\n return parseHTML(input);\n}\n\n/**\n * Checks if a string looks like JSON\n */\nexport function isJSON(str: string): boolean {\n const trimmed = str.trim();\n return trimmed.startsWith(\"{\") || trimmed.startsWith(\"[\");\n}\n\n/**\n * Reads input from stdin\n */\nexport async function readStdin(): Promise<string> {\n const chunks: Uint8Array[] = [];\n \n return new Promise((resolve, reject) => {\n process.stdin.on(\"data\", (chunk: Buffer | string) => {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk) : chunk);\n });\n process.stdin.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf-8\")));\n process.stdin.on(\"error\", reject);\n });\n}\n\n/**\n * Detects if stdin has data\n */\nexport function hasStdin(): boolean {\n return !process.stdin.isTTY;\n}\n\n","/**\n * Style guide file operations\n */\n\nimport { readFile, writeFile, mkdir } from \"fs/promises\";\nimport { existsSync } from \"fs\";\nimport { join, dirname } from \"path\";\n\nexport const STYLEGUIDE_PATHS = [\n \".uilint/styleguide.md\",\n \"styleguide.md\",\n \".uilint/style-guide.md\",\n];\n\n/**\n * Finds the style guide file in a project\n */\nexport function findStyleGuidePath(projectPath: string): string | null {\n for (const relativePath of STYLEGUIDE_PATHS) {\n const fullPath = join(projectPath, relativePath);\n if (existsSync(fullPath)) {\n return fullPath;\n }\n }\n return null;\n}\n\n/**\n * Reads the style guide content\n */\nexport async function readStyleGuide(path: string): Promise<string> {\n return readFile(path, \"utf-8\");\n}\n\n/**\n * Reads style guide from project path, finding it automatically\n */\nexport async function readStyleGuideFromProject(\n projectPath: string\n): Promise<string | null> {\n const path = findStyleGuidePath(projectPath);\n if (!path) return null;\n return readStyleGuide(path);\n}\n\n/**\n * Writes style guide content to file\n */\nexport async function writeStyleGuide(path: string, content: string): Promise<void> {\n const dir = dirname(path);\n await mkdir(dir, { recursive: true });\n await writeFile(path, content, \"utf-8\");\n}\n\n/**\n * Gets the default style guide path for a project\n */\nexport function getDefaultStyleGuidePath(projectPath: string): string {\n return join(projectPath, \".uilint\", \"styleguide.md\");\n}\n\n/**\n * Checks if a style guide exists\n */\nexport function styleGuideExists(projectPath: string): boolean {\n return findStyleGuidePath(projectPath) !== null;\n}\n\n","/**\n * Resolve the workspace/repo root from an arbitrary starting directory.\n *\n * This is primarily used in monorepos where runtime `process.cwd()` may point at\n * a sub-app (e.g. `apps/web`) but UI linting assets live at the workspace root.\n */\nimport { existsSync, readFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\n\nfunction hasWorkspacesField(dir: string): boolean {\n const pkgPath = join(dir, \"package.json\");\n if (!existsSync(pkgPath)) return false;\n try {\n const raw = readFileSync(pkgPath, \"utf-8\");\n const pkg = JSON.parse(raw) as { workspaces?: unknown };\n return !!pkg.workspaces;\n } catch {\n return false;\n }\n}\n\nfunction hasRootMarker(dir: string): boolean {\n // pnpm is the primary supported monorepo layout in this repo\n if (existsSync(join(dir, \"pnpm-workspace.yaml\"))) return true;\n // fallbacks for other setups\n if (existsSync(join(dir, \".git\"))) return true;\n if (hasWorkspacesField(dir)) return true;\n return false;\n}\n\n/**\n * Walks up from `startDir` to find a likely workspace root.\n *\n * If none is found, returns `startDir`.\n */\nexport function findWorkspaceRoot(startDir: string): string {\n let dir = startDir;\n let lastFallback: string | null = null;\n\n for (;;) {\n if (existsSync(join(dir, \"pnpm-workspace.yaml\"))) return dir;\n\n if (hasRootMarker(dir)) {\n // keep walking to prefer the highest-level marker (esp. .git)\n lastFallback = dir;\n }\n\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n\n return lastFallback || startDir;\n}\n\n","/**\n * Tailwind config reader (Node-only).\n *\n * Goals:\n * - Locate a Tailwind config file near a project directory.\n * - Load it (supports .ts via jiti).\n * - Produce compact token sets for styleguide + validation.\n *\n * Note: We intentionally extract tokens primarily from user-defined theme / extend\n * to avoid dumping Tailwind's full default palette into the style guide.\n */\n\nimport { existsSync } from \"fs\";\nimport { createRequire } from \"module\";\nimport { dirname, join } from \"path\";\nimport jiti from \"jiti\";\nimport type { TailwindThemeTokens } from \"../types.js\";\n\nconst CONFIG_CANDIDATES = [\n \"tailwind.config.ts\",\n \"tailwind.config.mts\",\n \"tailwind.config.cts\",\n \"tailwind.config.js\",\n \"tailwind.config.mjs\",\n \"tailwind.config.cjs\",\n];\n\nexport function findTailwindConfigPath(startDir: string): string | null {\n let dir = startDir;\n for (;;) {\n for (const name of CONFIG_CANDIDATES) {\n const full = join(dir, name);\n if (existsSync(full)) return full;\n }\n\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\nexport function readTailwindThemeTokens(\n projectDir: string\n): TailwindThemeTokens | null {\n const configPath = findTailwindConfigPath(projectDir);\n if (!configPath) return null;\n\n const loader = jiti(import.meta.url, { interopDefault: true });\n const loaded = loader(configPath) as any;\n const config = (loaded?.default ?? loaded) as any;\n if (!config || typeof config !== \"object\") return null;\n\n // Best-effort: run resolveConfig to ensure config is valid/normalized.\n // We don’t use the resolved theme for token enumeration (to avoid defaults),\n // but we do want to surface loader/shape problems early for debugging.\n try {\n const require = createRequire(import.meta.url);\n const maybe = require(\"tailwindcss/resolveConfig\");\n const resolveConfig = (maybe?.default ?? maybe) as\n | ((cfg: any) => any)\n | undefined;\n if (typeof resolveConfig === \"function\") resolveConfig(config);\n } catch {\n // If resolve fails, still attempt to extract from raw object.\n }\n\n const theme =\n config.theme && typeof config.theme === \"object\" ? config.theme : {};\n const extend =\n theme.extend && typeof theme.extend === \"object\" ? theme.extend : {};\n\n const colors = new Set<string>();\n const spacingKeys = new Set<string>();\n const borderRadiusKeys = new Set<string>();\n const fontFamilyKeys = new Set<string>();\n const fontSizeKeys = new Set<string>();\n\n // Merge base + extend per category.\n addColorTokens(colors, theme.colors);\n addColorTokens(colors, extend.colors);\n\n addKeys(spacingKeys, theme.spacing);\n addKeys(spacingKeys, extend.spacing);\n\n addKeys(borderRadiusKeys, theme.borderRadius);\n addKeys(borderRadiusKeys, extend.borderRadius);\n\n addKeys(fontFamilyKeys, theme.fontFamily);\n addKeys(fontFamilyKeys, extend.fontFamily);\n\n addKeys(fontSizeKeys, theme.fontSize);\n addKeys(fontSizeKeys, extend.fontSize);\n\n // If user config didn’t specify tokens, we still return an object to signal\n // \"tailwind detected\", but with empty sets (downstream can choose defaults).\n return {\n configPath,\n colors: [...colors],\n spacingKeys: [...spacingKeys],\n borderRadiusKeys: [...borderRadiusKeys],\n fontFamilyKeys: [...fontFamilyKeys],\n fontSizeKeys: [...fontSizeKeys],\n };\n}\n\nfunction addKeys(out: Set<string>, obj: unknown): void {\n if (!obj || typeof obj !== \"object\") return;\n for (const key of Object.keys(obj as Record<string, unknown>)) {\n if (!key) continue;\n out.add(key);\n }\n}\n\nfunction addColorTokens(out: Set<string>, colors: unknown): void {\n if (!colors || typeof colors !== \"object\") return;\n\n for (const [name, value] of Object.entries(\n colors as Record<string, unknown>\n )) {\n if (!name) continue;\n\n if (typeof value === \"string\") {\n out.add(`tailwind:${name}`);\n continue;\n }\n\n if (value && typeof value === \"object\" && !Array.isArray(value)) {\n for (const shade of Object.keys(value as Record<string, unknown>)) {\n if (!shade) continue;\n out.add(`tailwind:${name}-${shade}`);\n }\n continue;\n }\n\n // Arrays / functions etc are ignored for token enumeration.\n }\n}\n"],"mappings":"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","names":["existsSync","dirname","join","existsSync","dirname","join","require"]}
|
|
1
|
+
{"version":3,"sources":["../src/ollama/bootstrap.ts","../src/scanner/html-parser.ts","../src/styleguide/reader.ts","../src/utils/workspace-root.ts","../src/tailwind/config-reader.ts"],"sourcesContent":["/**\n * Ollama bootstrapping utilities (Node.js only).\n *\n * Goals:\n * - Detect whether Ollama is installed.\n * - Ensure the Ollama daemon is running (best effort: start `ollama serve` detached).\n * - Ensure a given model is pulled (best effort: run `ollama pull <model>`).\n */\n\nimport { spawn, spawnSync } from \"child_process\";\nimport readline from \"readline\";\nimport { OllamaClient } from \"./client.js\";\nimport { UILINT_DEFAULT_OLLAMA_MODEL } from \"./defaults.js\";\n\nconst DEFAULT_OLLAMA_BASE_URL = \"http://localhost:11434\";\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction isInteractiveTTY(): boolean {\n return Boolean(process.stdin.isTTY && process.stdout.isTTY);\n}\n\nasync function promptYesNo(question: string): Promise<boolean> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n return await new Promise<boolean>((resolve) => {\n rl.question(`${question} (y/N) `, (answer) => {\n rl.close();\n const normalized = (answer || \"\").trim().toLowerCase();\n resolve(normalized === \"y\" || normalized === \"yes\");\n });\n });\n}\n\nexport function isOllamaInstalled(): boolean {\n const result = spawnSync(\"ollama\", [\"--version\"], { stdio: \"ignore\" });\n if (\n result.error &&\n (result.error as NodeJS.ErrnoException).code === \"ENOENT\"\n ) {\n return false;\n }\n return result.status === 0;\n}\n\nfunction isBrewInstalled(): boolean {\n const result = spawnSync(\"brew\", [\"--version\"], { stdio: \"ignore\" });\n if (\n result.error &&\n (result.error as NodeJS.ErrnoException).code === \"ENOENT\"\n ) {\n return false;\n }\n return result.status === 0;\n}\n\nfunction getInstallInstructions(): string[] {\n const lines: string[] = [\n \"Ollama is required for LLM-backed analysis.\",\n \"\",\n \"Install Ollama:\",\n \" - Download: https://ollama.ai\",\n ];\n\n if (process.platform === \"darwin\") {\n lines.push(\" - Homebrew: brew install ollama\");\n }\n\n lines.push(\"\");\n lines.push(\"Then start it:\");\n lines.push(\" ollama serve\");\n return lines;\n}\n\nasync function maybeInstallOllamaWithBrew(): Promise<boolean> {\n if (process.platform !== \"darwin\") return false;\n if (!isInteractiveTTY()) return false;\n\n if (!isBrewInstalled()) {\n // We can't auto-install without brew; leave instructions to the caller.\n return false;\n }\n\n const ok = await promptYesNo(\n \"Ollama is not installed. Install with Homebrew now?\"\n );\n if (!ok) return false;\n\n await new Promise<void>((resolve, reject) => {\n const child = spawn(\"brew\", [\"install\", \"ollama\"], { stdio: \"inherit\" });\n child.on(\"error\", reject);\n child.on(\"exit\", (code) => {\n if (code === 0) resolve();\n else reject(new Error(`brew install ollama failed (exit ${code})`));\n });\n });\n\n return isOllamaInstalled();\n}\n\nexport async function ensureOllamaInstalledOrExplain(): Promise<void> {\n if (isOllamaInstalled()) return;\n\n const installedViaBrew = await maybeInstallOllamaWithBrew();\n if (installedViaBrew) return;\n\n const extra: string[] = [];\n if (process.platform === \"darwin\" && !isBrewInstalled()) {\n extra.push(\"\");\n extra.push(\"Homebrew is not installed. Install it first, then run:\");\n extra.push(\" brew install ollama\");\n }\n\n throw new Error([...getInstallInstructions(), ...extra].join(\"\\n\"));\n}\n\nexport async function ensureOllamaRunning(options?: {\n timeoutMs?: number;\n baseUrl?: string;\n}): Promise<void> {\n await ensureOllamaInstalledOrExplain();\n\n const baseUrl = options?.baseUrl || DEFAULT_OLLAMA_BASE_URL;\n const client = new OllamaClient({ baseUrl });\n const timeoutMs = options?.timeoutMs ?? 10_000;\n\n if (await client.isAvailable()) return;\n\n // Best-effort background start. We do not stop it later.\n try {\n const child = spawn(\"ollama\", [\"serve\"], {\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n } catch (err) {\n throw new Error(\n `Failed to start Ollama automatically.\\n\\n${getInstallInstructions().join(\n \"\\n\"\n )}\\n\\nDetails: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n if (await client.isAvailable()) return;\n await sleep(250);\n }\n\n throw new Error(\n [\n \"Ollama did not become ready in time.\",\n \"\",\n \"Try starting it manually:\",\n \" ollama serve\",\n ].join(\"\\n\")\n );\n}\n\nasync function fetchOllamaTags(baseUrl: string): Promise<string[]> {\n const res = await fetch(`${baseUrl}/api/tags`, {\n method: \"GET\",\n signal: AbortSignal.timeout(5000),\n });\n\n if (!res.ok) {\n throw new Error(`Ollama tags endpoint returned ${res.status}`);\n }\n\n const data = (await res.json()) as { models?: Array<{ name?: string }> };\n const names = (data.models ?? [])\n .map((m) => m.name)\n .filter((n): n is string => typeof n === \"string\");\n return names;\n}\n\nexport async function ensureOllamaModelPulled(options?: {\n model?: string;\n baseUrl?: string;\n}): Promise<void> {\n const desired = (options?.model || UILINT_DEFAULT_OLLAMA_MODEL).trim();\n if (!desired) return;\n\n const baseUrl = options?.baseUrl || DEFAULT_OLLAMA_BASE_URL;\n const tags = await fetchOllamaTags(baseUrl);\n if (tags.includes(desired)) return;\n\n await new Promise<void>((resolve, reject) => {\n const child = spawn(\"ollama\", [\"pull\", desired], { stdio: \"inherit\" });\n child.on(\"error\", reject);\n child.on(\"exit\", (code) => {\n if (code === 0) resolve();\n else reject(new Error(`ollama pull ${desired} failed (exit ${code})`));\n });\n });\n\n const tagsAfter = await fetchOllamaTags(baseUrl);\n if (!tagsAfter.includes(desired)) {\n throw new Error(\n `Model ${desired} did not appear in Ollama tags after pulling.`\n );\n }\n}\n\nexport async function ensureOllamaReady(options?: {\n model?: string;\n timeoutMs?: number;\n baseUrl?: string;\n}): Promise<void> {\n await ensureOllamaRunning({\n timeoutMs: options?.timeoutMs,\n baseUrl: options?.baseUrl,\n });\n await ensureOllamaModelPulled({\n model: options?.model,\n baseUrl: options?.baseUrl,\n });\n}\n","/**\n * HTML parser for extracting styles using JSDOM\n * Used by CLI and Node.js environments\n */\n\nimport { JSDOM } from \"jsdom\";\nimport type { ExtractedStyles, DOMSnapshot, SerializedStyles } from \"../types.js\";\nimport { extractStyles, deserializeStyles, truncateHTML } from \"./style-extractor.js\";\n\nexport interface ParseOptions {\n /**\n * If true, tries to load and process linked stylesheets\n */\n loadStylesheets?: boolean;\n /**\n * Max length for HTML in snapshot\n */\n maxHtmlLength?: number;\n}\n\n/**\n * Parses raw HTML and extracts styles using JSDOM\n */\nexport function parseHTML(\n html: string,\n options: ParseOptions = {}\n): DOMSnapshot {\n const { maxHtmlLength = 50000 } = options;\n\n const dom = new JSDOM(html, {\n runScripts: \"outside-only\",\n pretendToBeVisual: true,\n });\n\n const { document, getComputedStyle } = dom.window;\n const root = document.body || document.documentElement;\n\n const styles = extractStyles(root, getComputedStyle);\n const elementCount = root.querySelectorAll(\"*\").length;\n\n return {\n html: truncateHTML(html, maxHtmlLength),\n styles,\n elementCount,\n timestamp: Date.now(),\n };\n}\n\n/**\n * Input format for CLI - can be raw HTML or pre-extracted JSON\n */\nexport interface CLIInput {\n html: string;\n styles?: SerializedStyles;\n}\n\n/**\n * Parses CLI input which can be either raw HTML or JSON with pre-extracted styles\n */\nexport function parseCLIInput(input: string): DOMSnapshot {\n // Try to parse as JSON first\n try {\n const parsed = JSON.parse(input) as CLIInput;\n \n if (parsed.html && parsed.styles) {\n // Pre-extracted styles from browser/test environment\n return {\n html: truncateHTML(parsed.html),\n styles: deserializeStyles(parsed.styles),\n elementCount: 0, // Not available for pre-extracted\n timestamp: Date.now(),\n };\n } else if (parsed.html) {\n // Just HTML in JSON format\n return parseHTML(parsed.html);\n }\n } catch {\n // Not JSON, treat as raw HTML\n }\n\n // Parse as raw HTML\n return parseHTML(input);\n}\n\n/**\n * Checks if a string looks like JSON\n */\nexport function isJSON(str: string): boolean {\n const trimmed = str.trim();\n return trimmed.startsWith(\"{\") || trimmed.startsWith(\"[\");\n}\n\n/**\n * Reads input from stdin\n */\nexport async function readStdin(): Promise<string> {\n const chunks: Uint8Array[] = [];\n \n return new Promise((resolve, reject) => {\n process.stdin.on(\"data\", (chunk: Buffer | string) => {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk) : chunk);\n });\n process.stdin.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf-8\")));\n process.stdin.on(\"error\", reject);\n });\n}\n\n/**\n * Detects if stdin has data\n */\nexport function hasStdin(): boolean {\n return !process.stdin.isTTY;\n}\n\n","/**\n * Style guide file operations\n */\n\nimport { readFile, writeFile, mkdir } from \"fs/promises\";\nimport { existsSync } from \"fs\";\nimport { join, dirname } from \"path\";\n\nexport const STYLEGUIDE_PATHS = [\n \".uilint/styleguide.md\",\n \"styleguide.md\",\n \".uilint/style-guide.md\",\n];\n\n/**\n * Walk upward from a starting directory and look specifically for `.uilint/styleguide.md`.\n *\n * This is intended for flows where the \"project root\" is ambiguous (e.g., analyzing\n * an arbitrary file path) and we want the nearest `.uilint` config on the way up.\n */\nexport function findUILintStyleGuideUpwards(startDir: string): string | null {\n let dir = startDir;\n for (;;) {\n const candidate = join(dir, \".uilint\", \"styleguide.md\");\n if (existsSync(candidate)) return candidate;\n\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n/**\n * Finds the style guide file in a project\n */\nexport function findStyleGuidePath(projectPath: string): string | null {\n for (const relativePath of STYLEGUIDE_PATHS) {\n const fullPath = join(projectPath, relativePath);\n if (existsSync(fullPath)) {\n return fullPath;\n }\n }\n return null;\n}\n\n/**\n * Reads the style guide content\n */\nexport async function readStyleGuide(path: string): Promise<string> {\n return readFile(path, \"utf-8\");\n}\n\n/**\n * Reads style guide from project path, finding it automatically\n */\nexport async function readStyleGuideFromProject(\n projectPath: string\n): Promise<string | null> {\n const path = findStyleGuidePath(projectPath);\n if (!path) return null;\n return readStyleGuide(path);\n}\n\n/**\n * Writes style guide content to file\n */\nexport async function writeStyleGuide(\n path: string,\n content: string\n): Promise<void> {\n const dir = dirname(path);\n await mkdir(dir, { recursive: true });\n await writeFile(path, content, \"utf-8\");\n}\n\n/**\n * Gets the default style guide path for a project\n */\nexport function getDefaultStyleGuidePath(projectPath: string): string {\n return join(projectPath, \".uilint\", \"styleguide.md\");\n}\n\n/**\n * Checks if a style guide exists\n */\nexport function styleGuideExists(projectPath: string): boolean {\n return findStyleGuidePath(projectPath) !== null;\n}\n","/**\n * Resolve the workspace/repo root from an arbitrary starting directory.\n *\n * This is primarily used in monorepos where runtime `process.cwd()` may point at\n * a sub-app (e.g. `apps/web`) but UI linting assets live at the workspace root.\n */\nimport { existsSync, readFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\n\nfunction hasWorkspacesField(dir: string): boolean {\n const pkgPath = join(dir, \"package.json\");\n if (!existsSync(pkgPath)) return false;\n try {\n const raw = readFileSync(pkgPath, \"utf-8\");\n const pkg = JSON.parse(raw) as { workspaces?: unknown };\n return !!pkg.workspaces;\n } catch {\n return false;\n }\n}\n\nfunction hasRootMarker(dir: string): boolean {\n // pnpm is the primary supported monorepo layout in this repo\n if (existsSync(join(dir, \"pnpm-workspace.yaml\"))) return true;\n // fallbacks for other setups\n if (existsSync(join(dir, \".git\"))) return true;\n if (hasWorkspacesField(dir)) return true;\n return false;\n}\n\n/**\n * Walks up from `startDir` to find a likely workspace root.\n *\n * If none is found, returns `startDir`.\n */\nexport function findWorkspaceRoot(startDir: string): string {\n let dir = startDir;\n let lastFallback: string | null = null;\n\n for (;;) {\n if (existsSync(join(dir, \"pnpm-workspace.yaml\"))) return dir;\n\n if (hasRootMarker(dir)) {\n // keep walking to prefer the highest-level marker (esp. .git)\n lastFallback = dir;\n }\n\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n\n return lastFallback || startDir;\n}\n","/**\n * Tailwind config reader (Node-only).\n *\n * Goals:\n * - Locate a Tailwind config file near a project directory.\n * - Load it (supports .ts via jiti).\n * - Produce compact token sets for styleguide + validation.\n *\n * Note: We intentionally extract tokens primarily from user-defined theme / extend\n * to avoid dumping Tailwind's full default palette into the style guide.\n */\n\nimport { existsSync } from \"fs\";\nimport { createRequire } from \"module\";\nimport { dirname, join } from \"path\";\nimport jiti from \"jiti\";\nimport type { TailwindThemeTokens } from \"../types.js\";\n\nconst CONFIG_CANDIDATES = [\n \"tailwind.config.ts\",\n \"tailwind.config.mts\",\n \"tailwind.config.cts\",\n \"tailwind.config.js\",\n \"tailwind.config.mjs\",\n \"tailwind.config.cjs\",\n];\n\nexport function findTailwindConfigPath(startDir: string): string | null {\n let dir = startDir;\n for (;;) {\n for (const name of CONFIG_CANDIDATES) {\n const full = join(dir, name);\n if (existsSync(full)) return full;\n }\n\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\nexport function readTailwindThemeTokens(\n projectDir: string\n): TailwindThemeTokens | null {\n const configPath = findTailwindConfigPath(projectDir);\n if (!configPath) return null;\n\n const loader = jiti(import.meta.url, { interopDefault: true });\n const loaded = loader(configPath) as any;\n const config = (loaded?.default ?? loaded) as any;\n if (!config || typeof config !== \"object\") return null;\n\n // Best-effort: run resolveConfig to ensure config is valid/normalized.\n // We don’t use the resolved theme for token enumeration (to avoid defaults),\n // but we do want to surface loader/shape problems early for debugging.\n try {\n const require = createRequire(import.meta.url);\n const maybe = require(\"tailwindcss/resolveConfig\");\n const resolveConfig = (maybe?.default ?? maybe) as\n | ((cfg: any) => any)\n | undefined;\n if (typeof resolveConfig === \"function\") resolveConfig(config);\n } catch {\n // If resolve fails, still attempt to extract from raw object.\n }\n\n const theme =\n config.theme && typeof config.theme === \"object\" ? config.theme : {};\n const extend =\n theme.extend && typeof theme.extend === \"object\" ? theme.extend : {};\n\n const colors = new Set<string>();\n const spacingKeys = new Set<string>();\n const borderRadiusKeys = new Set<string>();\n const fontFamilyKeys = new Set<string>();\n const fontSizeKeys = new Set<string>();\n\n // Merge base + extend per category.\n addColorTokens(colors, theme.colors);\n addColorTokens(colors, extend.colors);\n\n addKeys(spacingKeys, theme.spacing);\n addKeys(spacingKeys, extend.spacing);\n\n addKeys(borderRadiusKeys, theme.borderRadius);\n addKeys(borderRadiusKeys, extend.borderRadius);\n\n addKeys(fontFamilyKeys, theme.fontFamily);\n addKeys(fontFamilyKeys, extend.fontFamily);\n\n addKeys(fontSizeKeys, theme.fontSize);\n addKeys(fontSizeKeys, extend.fontSize);\n\n // If user config didn’t specify tokens, we still return an object to signal\n // \"tailwind detected\", but with empty sets (downstream can choose defaults).\n return {\n configPath,\n colors: [...colors],\n spacingKeys: [...spacingKeys],\n borderRadiusKeys: [...borderRadiusKeys],\n fontFamilyKeys: [...fontFamilyKeys],\n fontSizeKeys: [...fontSizeKeys],\n };\n}\n\nfunction addKeys(out: Set<string>, obj: unknown): void {\n if (!obj || typeof obj !== \"object\") return;\n for (const key of Object.keys(obj as Record<string, unknown>)) {\n if (!key) continue;\n out.add(key);\n }\n}\n\nfunction addColorTokens(out: Set<string>, colors: unknown): void {\n if (!colors || typeof colors !== \"object\") return;\n\n for (const [name, value] of Object.entries(\n colors as Record<string, unknown>\n )) {\n if (!name) continue;\n\n if (typeof value === \"string\") {\n out.add(`tailwind:${name}`);\n continue;\n }\n\n if (value && typeof value === \"object\" && !Array.isArray(value)) {\n for (const shade of Object.keys(value as Record<string, unknown>)) {\n if (!shade) continue;\n out.add(`tailwind:${name}-${shade}`);\n }\n continue;\n }\n\n // Arrays / functions etc are ignored for token enumeration.\n }\n}\n"],"mappings":"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","names":["existsSync","dirname","join","existsSync","dirname","join","require"]}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ollama/prompts.ts","../src/ollama/defaults.ts","../src/ollama/client.ts","../src/format/issues.ts","../src/tailwind/class-tokens.ts","../src/scanner/style-extractor.ts","../src/styleguide/schema.ts","../src/styleguide/parser.ts","../src/styleguide/generator.ts","../src/consistency/prompts.ts","../src/consistency/analyzer.ts"],"sourcesContent":["/**\n * LLM prompt builders for UILint analysis\n */\n\n/**\n * Builds a prompt for style analysis\n */\nexport function buildAnalysisPrompt(\n styleSummary: string,\n styleGuide: string | null\n): string {\n const guideSection = styleGuide\n ? `## Current Style Guide\\n${styleGuide}\\n\\n`\n : \"## No Style Guide Found\\nAnalyze the styles and identify inconsistencies.\\n\\n\";\n\n return `You are a UI consistency analyzer. Analyze the following extracted styles and identify UI consistency violations.\n\n${guideSection}\n\n${styleSummary}\n\nRespond with JSON ONLY. Return a single JSON object containing an \"issues\" array. Each issue should have:\n- id: unique string identifier\n- type: one of \"color\", \"typography\", \"spacing\", \"component\", \"responsive\", \"accessibility\"\n- message: human-readable description of the issue\n- currentValue: the problematic value found\n- expectedValue: what it should be (if known from style guide)\n\nIMPORTANT:\n- Output ONLY violations. Do NOT include fix instructions, remediation steps, or \"suggestions\".\n- Do NOT include extra top-level keys. Only return {\"issues\":[...]}.\n- Keep messages short and neutral.\n\nFocus on:\n1. Similar but not identical colors (e.g., #3B82F6 vs #3575E2)\n2. Inconsistent font sizes or weights\n3. Spacing values that don't follow a consistent scale (e.g., 4px base unit)\n4. Mixed border-radius values\n5. If utility/Tailwind classes are present in the summary, treat them as the styling surface area and flag inconsistent utility usage (e.g., mixing px-4 and px-5 for the same component type)\n\nBe minimal. Only report significant inconsistencies.\n\nExample response:\n{\n \"issues\": [\n {\n ...issue fields...\n }\n ]\n}`;\n}\n\nexport interface BuildSourceAnalysisPromptOptions {\n /**\n * Optional filename/path for extra context in the prompt.\n */\n filePath?: string;\n /**\n * Optional hint about language (e.g. tsx, jsx, html).\n */\n languageHint?: string;\n /**\n * Optional additional context (e.g., Tailwind tokens, extracted utilities).\n * Keep this concise; it's appended verbatim.\n */\n extraContext?: string;\n}\n\n/**\n * Builds a prompt for analyzing a raw source file/snippet (TSX/JSX/etc) directly,\n * without attempting to parse it as HTML/DOM.\n */\nexport function buildSourceAnalysisPrompt(\n source: string,\n styleGuide: string | null,\n options: BuildSourceAnalysisPromptOptions = {}\n): string {\n const guideSection = styleGuide\n ? `## Current Style Guide\\n${styleGuide}\\n\\n`\n : \"## No Style Guide Found\\nAnalyze the UI code and identify inconsistencies.\\n\\n\";\n\n const metaLines: string[] = [];\n if (options.filePath) metaLines.push(`- filePath: ${options.filePath}`);\n if (options.languageHint)\n metaLines.push(`- languageHint: ${options.languageHint}`);\n const metaSection =\n metaLines.length > 0\n ? `## Source Metadata\\n${metaLines.join(\"\\n\")}\\n\\n`\n : \"\";\n\n const extra =\n options.extraContext && options.extraContext.trim()\n ? `## Additional Context\\n${options.extraContext.trim()}\\n\\n`\n : \"\";\n\n return `You are a UI consistency analyzer. Analyze the following UI source code and identify UI consistency violations.\n\n${guideSection}${metaSection}${extra}## Source Code (raw)\n${source}\n\nRespond with JSON ONLY. Return a single JSON object containing an \"issues\" array. Each issue should have:\n- id: unique string identifier\n- type: one of \"color\", \"typography\", \"spacing\", \"component\", \"responsive\", \"accessibility\"\n- message: human-readable description of the issue\n- currentValue: the problematic value found\n- expectedValue: what it should be (if known from style guide)\n\nIMPORTANT:\n- Output ONLY violations. Do NOT include fix instructions, remediation steps, or \"suggestions\".\n- Do NOT include extra top-level keys. Only return {\"issues\":[...]}.\n- Keep messages short and neutral.\n\nFocus on:\n1. Inconsistent Tailwind/utility classes (e.g., mixing px-4 and px-5 for the same component type)\n2. Inconsistent component variants (e.g., button sizes/radii/typography drift)\n3. Hardcoded colors/spacing/typography that should use tokens/scale\n4. Accessibility issues visible from code (e.g., missing labels, low-contrast combos if obvious)\n\nBe minimal. Only report significant inconsistencies.`;\n}\n\n/**\n * Builds a prompt for style guide generation\n */\nexport function buildStyleGuidePrompt(styleSummary: string): string {\n return `You are a design system expert. Based on the following detected styles, generate a clean, organized style guide in Markdown format.\n\n${styleSummary}\n\nGenerate a style guide with these sections:\n1. Colors - List the main colors with semantic names (Primary, Secondary, etc.)\n2. Typography - Font families, sizes, and weights\n3. Spacing - Base unit and common spacing values\n4. Components - Common component patterns\n5. Tailwind (if utility classes are present) - list commonly used utilities and any relevant theme tokens\n\nUse this format:\n# UI Style Guide\n\n## Colors\n- **Primary**: #HEXCODE (usage description)\n- **Secondary**: #HEXCODE (usage description)\n...\n\n## Typography\n- **Font Family**: FontName\n- **Font Sizes**: list of sizes\n- **Font Weights**: list of weights\n\n## Spacing\n- **Base unit**: Xpx\n- **Common values**: list of values\n\n## Components\n- **Buttons**: styles\n- **Cards**: styles\n...\n\nBe concise and focus on the most used values.`;\n}\n","/**\n * Single source of truth for Ollama defaults used across the monorepo.\n */\n\n/**\n * Default Ollama model used when none is provided.\n *\n * Note: This should match the model we recommend users `ollama pull`.\n */\nexport const UILINT_DEFAULT_OLLAMA_MODEL = \"qwen3-coder:30b\";\n\n","/**\n * Ollama API client for LLM interactions\n */\n\nimport type {\n UILintIssue,\n AnalysisResult,\n OllamaClientOptions,\n StreamProgressCallback,\n} from \"../types.js\";\nimport {\n buildAnalysisPrompt,\n buildSourceAnalysisPrompt,\n buildStyleGuidePrompt,\n type BuildSourceAnalysisPromptOptions,\n} from \"./prompts.js\";\nimport { UILINT_DEFAULT_OLLAMA_MODEL } from \"./defaults.js\";\n\nconst DEFAULT_BASE_URL = \"http://localhost:11434\";\nconst DEFAULT_TIMEOUT = 60000;\n\nexport class OllamaClient {\n private baseUrl: string;\n private model: string;\n private timeout: number;\n\n constructor(options: OllamaClientOptions = {}) {\n this.baseUrl = options.baseUrl || DEFAULT_BASE_URL;\n this.model = options.model || UILINT_DEFAULT_OLLAMA_MODEL;\n this.timeout = options.timeout || DEFAULT_TIMEOUT;\n }\n\n /**\n * Low-level completion API for custom prompts (used by installers/tools).\n *\n * When `json` is true, Ollama is requested to emit JSON (best-effort).\n */\n async complete(\n prompt: string,\n options: {\n json?: boolean;\n stream?: boolean;\n onProgress?: StreamProgressCallback;\n } = {}\n ): Promise<string> {\n const jsonFormat = options.json ?? false;\n if (options.stream && options.onProgress) {\n return await this.generateStreaming(\n prompt,\n jsonFormat,\n options.onProgress\n );\n }\n return await this.generate(prompt, jsonFormat);\n }\n\n /**\n * Analyzes styles and returns issues\n */\n async analyzeStyles(\n styleSummary: string,\n styleGuide: string | null,\n onProgress?: StreamProgressCallback\n ): Promise<AnalysisResult> {\n const startTime = Date.now();\n const prompt = buildAnalysisPrompt(styleSummary, styleGuide);\n\n try {\n const response = onProgress\n ? await this.generateStreaming(prompt, true, onProgress)\n : await this.generate(prompt);\n const issues = this.parseIssuesResponse(response);\n\n return {\n issues,\n analysisTime: Date.now() - startTime,\n };\n } catch (error) {\n console.error(\"[UILint] Analysis failed:\", error);\n return {\n issues: [],\n analysisTime: Date.now() - startTime,\n };\n }\n }\n\n /**\n * Analyzes a raw source file/snippet (TSX/JSX/etc) directly and returns issues.\n * This bypasses HTML/DOM parsing entirely.\n */\n async analyzeSource(\n source: string,\n styleGuide: string | null,\n onProgress?: StreamProgressCallback,\n options: BuildSourceAnalysisPromptOptions = {}\n ): Promise<AnalysisResult> {\n const startTime = Date.now();\n const prompt = buildSourceAnalysisPrompt(source, styleGuide, options);\n\n try {\n const response = onProgress\n ? await this.generateStreaming(prompt, true, onProgress)\n : await this.generate(prompt);\n const issues = this.parseIssuesResponse(response);\n\n return {\n issues,\n analysisTime: Date.now() - startTime,\n };\n } catch (error) {\n console.error(\"[UILint] Analysis failed:\", error);\n return {\n issues: [],\n analysisTime: Date.now() - startTime,\n };\n }\n }\n\n /**\n * Generates a style guide from detected styles\n */\n async generateStyleGuide(styleSummary: string): Promise<string | null> {\n const prompt = buildStyleGuidePrompt(styleSummary);\n\n try {\n const response = await this.generate(prompt, false);\n return response;\n } catch (error) {\n console.error(\"[UILint] Style guide generation failed:\", error);\n return null;\n }\n }\n\n /**\n * Core generate method that calls Ollama API\n */\n private async generate(\n prompt: string,\n jsonFormat: boolean = true\n ): Promise<string> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(`${this.baseUrl}/api/generate`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n model: this.model,\n prompt,\n stream: false,\n ...(jsonFormat && { format: \"json\" }),\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`Ollama API error: ${response.status}`);\n }\n\n const data = await response.json();\n return data.response || \"\";\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Streaming generate method that calls Ollama API with streaming\n * and reports progress via callback\n */\n private async generateStreaming(\n prompt: string,\n jsonFormat: boolean = true,\n onProgress: StreamProgressCallback\n ): Promise<string> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(`${this.baseUrl}/api/generate`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n model: this.model,\n prompt,\n stream: true,\n ...(jsonFormat && { format: \"json\" }),\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`Ollama API error: ${response.status}`);\n }\n\n if (!response.body) {\n throw new Error(\"No response body for streaming\");\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let fullResponse = \"\";\n let buffer = \"\";\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n\n // Process complete JSON lines from the buffer\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\"; // Keep incomplete line in buffer\n\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const chunk = JSON.parse(line);\n if (chunk.response) {\n fullResponse += chunk.response;\n // Get the latest line from the response for progress display\n const responseLines = fullResponse.split(\"\\n\");\n const latestLine =\n responseLines[responseLines.length - 1] ||\n responseLines[responseLines.length - 2] ||\n \"\";\n onProgress(latestLine.trim(), fullResponse);\n }\n } catch {\n // Skip malformed JSON chunks\n }\n }\n }\n\n // Process any remaining content in buffer\n if (buffer.trim()) {\n try {\n const chunk = JSON.parse(buffer);\n if (chunk.response) {\n fullResponse += chunk.response;\n }\n } catch {\n // Skip malformed JSON\n }\n }\n\n return fullResponse;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Parses issues from LLM response\n */\n private parseIssuesResponse(response: string): UILintIssue[] {\n try {\n const parsed = JSON.parse(response);\n return parsed.issues || [];\n } catch {\n console.warn(\"[UILint] Failed to parse LLM response as JSON\");\n return [];\n }\n }\n\n /**\n * Checks if Ollama is available\n */\n async isAvailable(): Promise<boolean> {\n try {\n const response = await fetch(`${this.baseUrl}/api/tags`, {\n method: \"GET\",\n signal: AbortSignal.timeout(5000),\n });\n return response.ok;\n } catch {\n return false;\n }\n }\n\n /**\n * Gets the current model\n */\n getModel(): string {\n return this.model;\n }\n\n /**\n * Sets the model\n */\n setModel(model: string): void {\n this.model = model;\n }\n}\n\n// Default singleton instance\nlet defaultClient: OllamaClient | null = null;\n\nexport function getOllamaClient(options?: OllamaClientOptions): OllamaClient {\n if (!defaultClient || options) {\n defaultClient = new OllamaClient(options);\n }\n return defaultClient;\n}\n","import type { UILintIssue } from \"../types.js\";\n\nexport interface FormatViolationsOptions {\n /**\n * Optional context label (e.g., filename) to include as a single header line.\n * Keep this empty for the most minimal output.\n */\n context?: string;\n /**\n * Include the trailing \"consult the style guide\" message.\n * Defaults to true.\n */\n includeFooter?: boolean;\n /**\n * Override the footer message.\n */\n footerMessage?: string;\n}\n\nconst DEFAULT_FOOTER = \"Consult the style guide for guidance.\";\n\n/**\n * Ensures issues are safe/minimal to print or serialize:\n * - drops `suggestion` (we only want violations)\n * - trims string fields\n */\nexport function sanitizeIssues(issues: UILintIssue[]): UILintIssue[] {\n return issues.map((issue) => ({\n id: String(issue.id ?? \"\").trim(),\n type: issue.type,\n message: String(issue.message ?? \"\").trim(),\n element: issue.element ? String(issue.element).trim() : undefined,\n selector: issue.selector ? String(issue.selector).trim() : undefined,\n currentValue: issue.currentValue ? String(issue.currentValue).trim() : undefined,\n expectedValue: issue.expectedValue\n ? String(issue.expectedValue).trim()\n : undefined,\n // Intentionally omit `suggestion`\n }));\n}\n\n/**\n * Minimal human-readable rendering of violations.\n * Intended for CLI/MCP text output.\n */\nexport function formatViolationsText(\n issues: UILintIssue[],\n options: FormatViolationsOptions = {}\n): string {\n const { context, includeFooter = true, footerMessage = DEFAULT_FOOTER } =\n options;\n\n if (!issues || issues.length === 0) {\n return \"No violations found.\";\n }\n\n const lines: string[] = [];\n\n if (context && context.trim()) {\n lines.push(`Violations in ${context.trim()}:`);\n lines.push(\"\");\n }\n\n const sanitized = sanitizeIssues(issues);\n sanitized.forEach((issue, i) => {\n lines.push(`${i + 1}. [${issue.type}] ${issue.message}`);\n if (issue.currentValue && issue.expectedValue) {\n lines.push(` ${issue.currentValue} → ${issue.expectedValue}`);\n } else if (issue.currentValue) {\n lines.push(` ${issue.currentValue}`);\n }\n lines.push(\"\");\n });\n\n // remove trailing blank line\n while (lines.length > 0 && lines[lines.length - 1] === \"\") lines.pop();\n\n if (includeFooter) {\n lines.push(\"\");\n lines.push(footerMessage);\n }\n\n return lines.join(\"\\n\");\n}\n","/**\n * Tailwind / utility-class token extraction helpers.\n *\n * Notes:\n * - Works on HTML (class=\"...\") and TSX-ish input (className=\"...\") via regex.\n * - Variant parsing is bracket-aware so arbitrary values like bg-[color:var(--x)]\n * don't get split incorrectly on \":\".\n */\n\nexport interface ClassTokenCounts {\n /**\n * Base utilities with variants stripped.\n * Example: \"sm:hover:bg-gray-50\" => utility \"bg-gray-50\"\n */\n utilities: Map<string, number>;\n /**\n * Individual variant prefixes.\n * Example: \"sm:hover:bg-gray-50\" => variants \"sm\", \"hover\"\n */\n variants: Map<string, number>;\n}\n\nexport interface ExtractClassTokenOptions {\n /**\n * Maximum number of tokens to process (guardrail for very large inputs).\n */\n maxTokens?: number;\n}\n\nexport function extractClassTokensFromHtml(\n html: string,\n options: ExtractClassTokenOptions = {}\n): ClassTokenCounts {\n const { maxTokens = 20000 } = options;\n\n const utilities = new Map<string, number>();\n const variants = new Map<string, number>();\n\n if (!html) return { utilities, variants };\n\n // Match both HTML and JSX-ish attributes.\n // - class=\"...\"\n // - className=\"...\"\n const attrPattern = /\\b(?:class|className)\\s*=\\s*[\"']([^\"']+)[\"']/g;\n\n let tokenBudget = maxTokens;\n let match: RegExpExecArray | null;\n while ((match = attrPattern.exec(html)) && tokenBudget > 0) {\n const raw = match[1];\n if (!raw) continue;\n\n const tokens = raw.split(/\\s+/g).filter(Boolean);\n for (const token of tokens) {\n if (tokenBudget-- <= 0) break;\n\n const { base, variantList } = splitVariants(token);\n const normalizedBase = normalizeUtility(base);\n if (!normalizedBase) continue;\n\n increment(utilities, normalizedBase);\n for (const v of variantList) increment(variants, v);\n }\n }\n\n return { utilities, variants };\n}\n\nexport function topEntries(\n map: Map<string, number>,\n limit: number\n): Array<{ token: string; count: number }> {\n return [...map.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, limit)\n .map(([token, count]) => ({ token, count }));\n}\n\nfunction increment(map: Map<string, number>, key: string): void {\n map.set(key, (map.get(key) || 0) + 1);\n}\n\nfunction normalizeUtility(token: string): string | null {\n const t = token.trim();\n if (!t) return null;\n\n // Strip important modifier\n const noImportant = t.startsWith(\"!\") ? t.slice(1) : t;\n\n // Ignore obviously non-class tokens\n if (!noImportant || noImportant === \"{\" || noImportant === \"}\") return null;\n\n return noImportant;\n}\n\n/**\n * Splits a class token into variants and base utility.\n * Bracket-aware to avoid splitting arbitrary values on \":\".\n *\n * Examples:\n * - \"sm:hover:bg-gray-50\" => variants [\"sm\",\"hover\"], base \"bg-gray-50\"\n * - \"bg-[color:var(--x)]\" => variants [], base \"bg-[color:var(--x)]\"\n * - \"sm:bg-[color:var(--x)]\" => variants [\"sm\"], base \"bg-[color:var(--x)]\"\n */\nfunction splitVariants(token: string): { base: string; variantList: string[] } {\n const parts: string[] = [];\n let buf = \"\";\n let bracketDepth = 0;\n\n for (let i = 0; i < token.length; i++) {\n const ch = token[i];\n if (ch === \"[\") bracketDepth++;\n if (ch === \"]\" && bracketDepth > 0) bracketDepth--;\n\n if (ch === \":\" && bracketDepth === 0) {\n parts.push(buf);\n buf = \"\";\n continue;\n }\n\n buf += ch;\n }\n parts.push(buf);\n\n if (parts.length <= 1) return { base: token, variantList: [] };\n\n const base = parts[parts.length - 1] || \"\";\n const variantList = parts\n .slice(0, -1)\n .map((v) => v.trim())\n .filter(Boolean);\n\n return { base, variantList };\n}\n\n","/**\n * Style extraction from DOM elements\n */\n\nimport type {\n ExtractedStyles,\n SerializedStyles,\n TailwindThemeTokens,\n} from \"../types.js\";\nimport {\n extractClassTokensFromHtml,\n topEntries,\n} from \"../tailwind/class-tokens.js\";\n\n/**\n * Extracts all computed styles from elements in the document\n * Works in both browser and JSDOM environments\n */\nexport function extractStyles(\n root: Element | Document,\n getComputedStyle: (el: Element) => CSSStyleDeclaration\n): ExtractedStyles {\n const styles: ExtractedStyles = {\n colors: new Map(),\n fontSizes: new Map(),\n fontFamilies: new Map(),\n fontWeights: new Map(),\n spacing: new Map(),\n borderRadius: new Map(),\n };\n\n const elements = root.querySelectorAll(\"*\");\n\n elements.forEach((element) => {\n // nodeType === 1 means Element node (works in both browser and JSDOM)\n if (element.nodeType !== 1) return;\n\n const computed = getComputedStyle(element);\n\n // Extract colors\n extractColor(computed.color, styles.colors);\n extractColor(computed.backgroundColor, styles.colors);\n extractColor(computed.borderColor, styles.colors);\n\n // Extract typography\n incrementMap(styles.fontSizes, computed.fontSize);\n incrementMap(styles.fontFamilies, normalizeFontFamily(computed.fontFamily));\n incrementMap(styles.fontWeights, computed.fontWeight);\n\n // Extract spacing\n extractSpacing(computed.margin, styles.spacing);\n extractSpacing(computed.padding, styles.spacing);\n incrementMap(styles.spacing, computed.gap);\n\n // Extract border radius\n incrementMap(styles.borderRadius, computed.borderRadius);\n });\n\n return styles;\n}\n\n/**\n * Extracts styles from browser DOM (uses window.getComputedStyle)\n */\nexport function extractStylesFromDOM(\n root?: Element | Document\n): ExtractedStyles {\n const targetRoot = root || document.body;\n return extractStyles(targetRoot, (el) => window.getComputedStyle(el));\n}\n\nfunction extractColor(color: string, map: Map<string, number>): void {\n if (!color || color === \"transparent\" || color === \"rgba(0, 0, 0, 0)\") return;\n\n // Normalize to hex\n const hex = rgbToHex(color);\n if (hex) {\n incrementMap(map, hex);\n }\n}\n\nfunction extractSpacing(value: string, map: Map<string, number>): void {\n if (!value || value === \"0px\") return;\n\n // Split compound values (e.g., \"10px 20px 10px 20px\")\n const values = value.split(\" \").filter((v) => v && v !== \"0px\");\n values.forEach((v) => incrementMap(map, v));\n}\n\nfunction incrementMap(map: Map<string, number>, value: string): void {\n if (!value || value === \"normal\" || value === \"auto\") return;\n map.set(value, (map.get(value) || 0) + 1);\n}\n\nfunction normalizeFontFamily(fontFamily: string): string {\n // Get the primary font (first in the stack)\n const primary = fontFamily.split(\",\")[0].trim();\n return primary.replace(/['\"]/g, \"\");\n}\n\nfunction rgbToHex(rgb: string): string | null {\n // Handle hex values\n if (rgb.startsWith(\"#\")) return rgb.toUpperCase();\n\n // Handle rgb/rgba values\n const match = rgb.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/);\n if (!match) return null;\n\n const [, r, g, b] = match;\n const toHex = (n: string) => parseInt(n).toString(16).padStart(2, \"0\");\n\n return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase();\n}\n\n/**\n * Converts ExtractedStyles maps to plain objects for serialization\n */\nexport function serializeStyles(styles: ExtractedStyles): SerializedStyles {\n return {\n colors: Object.fromEntries(styles.colors),\n fontSizes: Object.fromEntries(styles.fontSizes),\n fontFamilies: Object.fromEntries(styles.fontFamilies),\n fontWeights: Object.fromEntries(styles.fontWeights),\n spacing: Object.fromEntries(styles.spacing),\n borderRadius: Object.fromEntries(styles.borderRadius),\n };\n}\n\n/**\n * Converts SerializedStyles back to ExtractedStyles\n */\nexport function deserializeStyles(\n serialized: SerializedStyles\n): ExtractedStyles {\n return {\n colors: new Map(Object.entries(serialized.colors)),\n fontSizes: new Map(Object.entries(serialized.fontSizes)),\n fontFamilies: new Map(Object.entries(serialized.fontFamilies)),\n fontWeights: new Map(Object.entries(serialized.fontWeights)),\n spacing: new Map(Object.entries(serialized.spacing)),\n borderRadius: new Map(Object.entries(serialized.borderRadius)),\n };\n}\n\n/**\n * Creates a summary of extracted styles for LLM analysis\n */\nexport function createStyleSummary(\n styles: ExtractedStyles,\n options: CreateStyleSummaryOptions = {}\n): string {\n return createStyleSummaryWithOptions(styles, options);\n}\n\nexport interface CreateStyleSummaryOptions {\n /**\n * Optional HTML/TSX-ish string used to extract utility classes (Tailwind etc).\n */\n html?: string;\n /**\n * Optional Tailwind theme tokens (typically from tailwind.config.*).\n */\n tailwindTheme?: TailwindThemeTokens | null;\n}\n\n/**\n * Creates a summary of extracted styles for LLM analysis.\n * Accepts optional Tailwind context to make Tailwind-heavy projects analyzable\n * even when computed styles are sparse (e.g., JSDOM without loaded CSS).\n */\nexport function createStyleSummaryWithOptions(\n styles: ExtractedStyles,\n options: CreateStyleSummaryOptions = {}\n): string {\n const lines: string[] = [];\n\n lines.push(\"## Detected Styles Summary\\n\");\n\n // Colors\n lines.push(\"### Colors\");\n const sortedColors = [...styles.colors.entries()].sort((a, b) => b[1] - a[1]);\n sortedColors.slice(0, 20).forEach(([color, count]) => {\n lines.push(`- ${color}: ${count} occurrences`);\n });\n lines.push(\"\");\n\n // Font sizes\n lines.push(\"### Font Sizes\");\n const sortedFontSizes = [...styles.fontSizes.entries()].sort(\n (a, b) => b[1] - a[1]\n );\n sortedFontSizes.forEach(([size, count]) => {\n lines.push(`- ${size}: ${count} occurrences`);\n });\n lines.push(\"\");\n\n // Font families\n lines.push(\"### Font Families\");\n const sortedFontFamilies = [...styles.fontFamilies.entries()].sort(\n (a, b) => b[1] - a[1]\n );\n sortedFontFamilies.forEach(([family, count]) => {\n lines.push(`- ${family}: ${count} occurrences`);\n });\n lines.push(\"\");\n\n // Font weights\n lines.push(\"### Font Weights\");\n const sortedFontWeights = [...styles.fontWeights.entries()].sort(\n (a, b) => b[1] - a[1]\n );\n sortedFontWeights.forEach(([weight, count]) => {\n lines.push(`- ${weight}: ${count} occurrences`);\n });\n lines.push(\"\");\n\n // Spacing\n lines.push(\"### Spacing Values\");\n const sortedSpacing = [...styles.spacing.entries()].sort(\n (a, b) => b[1] - a[1]\n );\n sortedSpacing.slice(0, 15).forEach(([value, count]) => {\n lines.push(`- ${value}: ${count} occurrences`);\n });\n lines.push(\"\");\n\n // Border radius\n lines.push(\"### Border Radius\");\n const sortedBorderRadius = [...styles.borderRadius.entries()].sort(\n (a, b) => b[1] - a[1]\n );\n sortedBorderRadius.forEach(([value, count]) => {\n lines.push(`- ${value}: ${count} occurrences`);\n });\n\n // Tailwind / utility classes (optional)\n if (options.html) {\n const tokens = extractClassTokensFromHtml(options.html);\n const topUtilities = topEntries(tokens.utilities, 40);\n const topVariants = topEntries(tokens.variants, 15);\n\n lines.push(\"\");\n lines.push(\"### Utility Classes (from markup)\");\n if (topUtilities.length === 0) {\n lines.push(\"- (none detected)\");\n } else {\n topUtilities.forEach(({ token, count }) => {\n lines.push(`- ${token}: ${count} occurrences`);\n });\n }\n\n if (topVariants.length > 0) {\n lines.push(\"\");\n lines.push(\"### Common Variants\");\n topVariants.forEach(({ token, count }) => {\n lines.push(`- ${token}: ${count} occurrences`);\n });\n }\n }\n\n // Tailwind theme tokens (optional)\n if (options.tailwindTheme) {\n const tt = options.tailwindTheme;\n lines.push(\"\");\n lines.push(\"### Tailwind Theme Tokens (from config)\");\n lines.push(`- configPath: ${tt.configPath}`);\n lines.push(`- colors: ${tt.colors.length}`);\n lines.push(`- spacingKeys: ${tt.spacingKeys.length}`);\n lines.push(`- borderRadiusKeys: ${tt.borderRadiusKeys.length}`);\n lines.push(`- fontFamilyKeys: ${tt.fontFamilyKeys.length}`);\n lines.push(`- fontSizeKeys: ${tt.fontSizeKeys.length}`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Truncates HTML to a maximum length\n */\nexport function truncateHTML(html: string, maxLength: number = 50000): string {\n if (html.length <= maxLength) return html;\n return html.slice(0, maxLength) + \"<!-- truncated -->\";\n}\n","/**\n * Style guide schema and utilities\n */\n\nimport type { StyleGuide, ColorRule, TypographyRule, SpacingRule, ComponentRule } from \"../types.js\";\n\n/**\n * Creates an empty style guide structure\n */\nexport function createEmptyStyleGuide(): StyleGuide {\n return {\n colors: [],\n typography: [],\n spacing: [],\n components: [],\n };\n}\n\n/**\n * Validates a style guide object\n */\nexport function validateStyleGuide(guide: unknown): guide is StyleGuide {\n if (!guide || typeof guide !== \"object\") return false;\n\n const g = guide as Record<string, unknown>;\n\n return (\n Array.isArray(g.colors) &&\n Array.isArray(g.typography) &&\n Array.isArray(g.spacing) &&\n Array.isArray(g.components)\n );\n}\n\n/**\n * Merges detected styles into an existing style guide\n */\nexport function mergeStyleGuides(\n existing: StyleGuide,\n detected: Partial<StyleGuide>\n): StyleGuide {\n return {\n colors: mergeColorRules(existing.colors, detected.colors || []),\n typography: mergeTypographyRules(existing.typography, detected.typography || []),\n spacing: mergeSpacingRules(existing.spacing, detected.spacing || []),\n components: mergeComponentRules(existing.components, detected.components || []),\n };\n}\n\nfunction mergeColorRules(existing: ColorRule[], detected: ColorRule[]): ColorRule[] {\n const merged = [...existing];\n detected.forEach((rule) => {\n const existingIndex = merged.findIndex(\n (e) => e.name === rule.name || e.value === rule.value\n );\n if (existingIndex === -1) {\n merged.push(rule);\n }\n });\n return merged;\n}\n\nfunction mergeTypographyRules(existing: TypographyRule[], detected: TypographyRule[]): TypographyRule[] {\n const merged = [...existing];\n detected.forEach((rule) => {\n const existingIndex = merged.findIndex((e) => e.element === rule.element);\n if (existingIndex === -1) {\n merged.push(rule);\n }\n });\n return merged;\n}\n\nfunction mergeSpacingRules(existing: SpacingRule[], detected: SpacingRule[]): SpacingRule[] {\n const merged = [...existing];\n detected.forEach((rule) => {\n const existingIndex = merged.findIndex(\n (e) => e.name === rule.name || e.value === rule.value\n );\n if (existingIndex === -1) {\n merged.push(rule);\n }\n });\n return merged;\n}\n\nfunction mergeComponentRules(existing: ComponentRule[], detected: ComponentRule[]): ComponentRule[] {\n const merged = [...existing];\n detected.forEach((rule) => {\n const existingIndex = merged.findIndex((e) => e.name === rule.name);\n if (existingIndex === -1) {\n merged.push(rule);\n }\n });\n return merged;\n}\n\n/**\n * Creates a color rule\n */\nexport function createColorRule(\n name: string,\n value: string,\n usage: string = \"\"\n): ColorRule {\n return { name, value: value.toUpperCase(), usage };\n}\n\n/**\n * Creates a typography rule\n */\nexport function createTypographyRule(\n element: string,\n options: Partial<Omit<TypographyRule, \"element\">> = {}\n): TypographyRule {\n return { element, ...options };\n}\n\n/**\n * Creates a spacing rule\n */\nexport function createSpacingRule(name: string, value: string): SpacingRule {\n return { name, value };\n}\n\n/**\n * Creates a component rule\n */\nexport function createComponentRule(name: string, styles: string[]): ComponentRule {\n return { name, styles };\n}\n\n","/**\n * Parse Markdown style guides into structured data\n */\n\nimport type {\n StyleGuide,\n ColorRule,\n TypographyRule,\n SpacingRule,\n ComponentRule,\n ExtractedStyleValues,\n} from \"../types.js\";\nimport { createEmptyStyleGuide } from \"./schema.js\";\n\n/**\n * Parses a Markdown style guide into a structured object\n */\nexport function parseStyleGuide(markdown: string): StyleGuide {\n const guide = createEmptyStyleGuide();\n const sections = splitIntoSections(markdown);\n\n sections.forEach(({ title, content }) => {\n const lowerTitle = title.toLowerCase();\n\n if (lowerTitle.includes(\"color\")) {\n guide.colors = parseColorSection(content);\n } else if (\n lowerTitle.includes(\"typography\") ||\n lowerTitle.includes(\"font\")\n ) {\n guide.typography = parseTypographySection(content);\n } else if (lowerTitle.includes(\"spacing\")) {\n guide.spacing = parseSpacingSection(content);\n } else if (lowerTitle.includes(\"component\")) {\n guide.components = parseComponentSection(content);\n }\n });\n\n return guide;\n}\n\ninterface Section {\n title: string;\n content: string;\n}\n\nfunction splitIntoSections(markdown: string): Section[] {\n const sections: Section[] = [];\n const lines = markdown.split(\"\\n\");\n\n let currentTitle = \"\";\n let currentContent: string[] = [];\n\n lines.forEach((line) => {\n const headerMatch = line.match(/^##\\s+(.+)$/);\n\n if (headerMatch) {\n if (currentTitle) {\n sections.push({\n title: currentTitle,\n content: currentContent.join(\"\\n\"),\n });\n }\n currentTitle = headerMatch[1];\n currentContent = [];\n } else {\n currentContent.push(line);\n }\n });\n\n if (currentTitle) {\n sections.push({\n title: currentTitle,\n content: currentContent.join(\"\\n\"),\n });\n }\n\n return sections;\n}\n\nfunction parseColorSection(content: string): ColorRule[] {\n const colors: ColorRule[] = [];\n const lines = content.split(\"\\n\");\n\n lines.forEach((line) => {\n // Match patterns like: - **Primary**: #3B82F6 (used in buttons)\n const match = line.match(\n /[-*]\\s*\\*?\\*?([^*:]+)\\*?\\*?:\\s*(#[A-Fa-f0-9]{6})\\s*(?:\\(([^)]+)\\))?/\n );\n\n if (match) {\n colors.push({\n name: match[1].trim(),\n value: match[2].toUpperCase(),\n usage: match[3] || \"\",\n });\n }\n });\n\n return colors;\n}\n\nfunction parseTypographySection(content: string): TypographyRule[] {\n const typography: TypographyRule[] = [];\n const lines = content.split(\"\\n\");\n\n lines.forEach((line) => {\n // Match patterns like: - **Headings**: font-family: \"Inter\", font-size: 24px\n const elementMatch = line.match(/[-*]\\s*\\*?\\*?([^*:]+)\\*?\\*?:\\s*(.+)/);\n\n if (elementMatch) {\n const rule: TypographyRule = {\n element: elementMatch[1].trim(),\n };\n\n const props = elementMatch[2];\n\n const fontFamilyMatch = props.match(/font-family:\\s*\"?([^\",]+)\"?/);\n if (fontFamilyMatch) rule.fontFamily = fontFamilyMatch[1].trim();\n\n const fontSizeMatch = props.match(/font-size:\\s*(\\d+px)/);\n if (fontSizeMatch) rule.fontSize = fontSizeMatch[1];\n\n const fontWeightMatch = props.match(/font-weight:\\s*(\\d+)/);\n if (fontWeightMatch) rule.fontWeight = fontWeightMatch[1];\n\n const lineHeightMatch = props.match(/line-height:\\s*([\\d.]+)/);\n if (lineHeightMatch) rule.lineHeight = lineHeightMatch[1];\n\n typography.push(rule);\n }\n });\n\n return typography;\n}\n\nfunction parseSpacingSection(content: string): SpacingRule[] {\n const spacing: SpacingRule[] = [];\n const lines = content.split(\"\\n\");\n\n lines.forEach((line) => {\n // Match patterns like: - **Base unit**: 4px\n const match = line.match(/[-*]\\s*\\*?\\*?([^*:]+)\\*?\\*?:\\s*(.+)/);\n\n if (match) {\n spacing.push({\n name: match[1].trim(),\n value: match[2].trim(),\n });\n }\n });\n\n return spacing;\n}\n\nfunction parseComponentSection(content: string): ComponentRule[] {\n const components: ComponentRule[] = [];\n const lines = content.split(\"\\n\");\n\n lines.forEach((line) => {\n // Match patterns like: - **Buttons**: rounded-lg, px-4 py-2\n const match = line.match(/[-*]\\s*\\*?\\*?([^*:]+)\\*?\\*?:\\s*(.+)/);\n\n if (match) {\n components.push({\n name: match[1].trim(),\n styles: match[2].split(\",\").map((s) => s.trim()),\n });\n }\n });\n\n return components;\n}\n\n/**\n * Parses sections from a Markdown style guide (simpler format)\n */\nexport function parseStyleGuideSections(\n content: string\n): Record<string, string> {\n const sections: Record<string, string> = {};\n const lines = content.split(\"\\n\");\n\n let currentSection = \"intro\";\n let currentContent: string[] = [];\n\n for (const line of lines) {\n const headerMatch = line.match(/^##\\s+(.+)$/);\n\n if (headerMatch) {\n if (currentContent.length > 0) {\n sections[currentSection.toLowerCase()] = currentContent\n .join(\"\\n\")\n .trim();\n }\n\n currentSection = headerMatch[1];\n currentContent = [];\n } else {\n currentContent.push(line);\n }\n }\n\n if (currentContent.length > 0) {\n sections[currentSection.toLowerCase()] = currentContent.join(\"\\n\").trim();\n }\n\n return sections;\n}\n\n/**\n * Extracts specific values from the style guide\n */\nexport function extractStyleValues(content: string): ExtractedStyleValues {\n const result: ExtractedStyleValues = {\n colors: [],\n fontSizes: [],\n fontFamilies: [],\n spacing: [],\n borderRadius: [],\n };\n\n // Extract hex colors\n const colorMatches = content.matchAll(/#[A-Fa-f0-9]{6}\\b/g);\n for (const match of colorMatches) {\n if (!result.colors.includes(match[0].toUpperCase())) {\n result.colors.push(match[0].toUpperCase());\n }\n }\n\n // Extract font sizes (e.g., 16px, 1.5rem)\n const fontSizeMatches = content.matchAll(/\\b(\\d+(?:\\.\\d+)?(?:px|rem|em))\\b/g);\n for (const match of fontSizeMatches) {\n if (!result.fontSizes.includes(match[1])) {\n result.fontSizes.push(match[1]);\n }\n }\n\n // Extract font families (quoted strings in font context)\n const fontFamilyMatches = content.matchAll(\n /font-family:\\s*[\"']?([^\"',\\n]+)/gi\n );\n for (const match of fontFamilyMatches) {\n const family = match[1].trim();\n if (!result.fontFamilies.includes(family)) {\n result.fontFamilies.push(family);\n }\n }\n\n return result;\n}\n\nexport interface TailwindAllowlist {\n allowAnyColor: boolean;\n allowStandardSpacing: boolean;\n allowedTailwindColors: Set<string>;\n allowedUtilities: Set<string>;\n allowedSpacingKeys: Set<string>;\n allowedBorderRadiusKeys: Set<string>;\n allowedFontSizeKeys: Set<string>;\n allowedFontFamilyKeys: Set<string>;\n}\n\n/**\n * Extract Tailwind / utility-class allowlist configuration from a style guide.\n *\n * Expected formats:\n * - A JSON code block inside a \"## Tailwind\" section (preferred; produced by UILint)\n * - Fallback: inline backticked utilities within the Tailwind section\n */\nexport function extractTailwindAllowlist(content: string): TailwindAllowlist {\n const empty: TailwindAllowlist = {\n allowAnyColor: false,\n allowStandardSpacing: false,\n allowedTailwindColors: new Set(),\n allowedUtilities: new Set(),\n allowedSpacingKeys: new Set(),\n allowedBorderRadiusKeys: new Set(),\n allowedFontSizeKeys: new Set(),\n allowedFontFamilyKeys: new Set(),\n };\n\n // Only look for allowlist details in the Tailwind section.\n const sections = parseStyleGuideSections(content);\n const tailwindSection =\n sections[\"tailwind\"] ??\n // defensive: some styleguides use different casing/spacing\n sections[\"tailwind utilities\"] ??\n \"\";\n\n if (!tailwindSection) return empty;\n\n const parsed = tryParseFirstJsonCodeBlock(tailwindSection);\n if (parsed && typeof parsed === \"object\") {\n const allowAnyColor = Boolean((parsed as any).allowAnyColor);\n const allowStandardSpacing = Boolean((parsed as any).allowStandardSpacing);\n\n const allowedUtilitiesArr = Array.isArray((parsed as any).allowedUtilities)\n ? ((parsed as any).allowedUtilities as unknown[]).filter(\n (u): u is string => typeof u === \"string\"\n )\n : [];\n\n const themeTokens = (parsed as any).themeTokens ?? {};\n const themeColors = Array.isArray(themeTokens.colors)\n ? (themeTokens.colors as unknown[]).filter(\n (c): c is string => typeof c === \"string\"\n )\n : [];\n const spacingKeys = Array.isArray(themeTokens.spacingKeys)\n ? (themeTokens.spacingKeys as unknown[]).filter(\n (k): k is string => typeof k === \"string\"\n )\n : [];\n const borderRadiusKeys = Array.isArray(themeTokens.borderRadiusKeys)\n ? (themeTokens.borderRadiusKeys as unknown[]).filter(\n (k): k is string => typeof k === \"string\"\n )\n : [];\n const fontFamilyKeys = Array.isArray(themeTokens.fontFamilyKeys)\n ? (themeTokens.fontFamilyKeys as unknown[]).filter(\n (k): k is string => typeof k === \"string\"\n )\n : [];\n const fontSizeKeys = Array.isArray(themeTokens.fontSizeKeys)\n ? (themeTokens.fontSizeKeys as unknown[]).filter(\n (k): k is string => typeof k === \"string\"\n )\n : [];\n\n const allowedTailwindColors = new Set<string>();\n for (const c of themeColors) {\n const raw = c.trim();\n if (!raw) continue;\n if (raw.toLowerCase().startsWith(\"tailwind:\")) {\n allowedTailwindColors.add(raw.toLowerCase());\n continue;\n }\n const m = raw.match(/^([a-zA-Z]+)-(\\d{2,3})$/);\n if (m) {\n allowedTailwindColors.add(`tailwind:${m[1].toLowerCase()}-${m[2]}`);\n }\n }\n\n return {\n allowAnyColor,\n allowStandardSpacing,\n allowedTailwindColors,\n allowedUtilities: new Set(\n allowedUtilitiesArr.map((s) => s.trim()).filter(Boolean)\n ),\n allowedSpacingKeys: new Set(\n spacingKeys.map((s) => s.trim()).filter(Boolean)\n ),\n allowedBorderRadiusKeys: new Set(\n borderRadiusKeys.map((s) => s.trim()).filter(Boolean)\n ),\n allowedFontSizeKeys: new Set(\n fontSizeKeys.map((s) => s.trim()).filter(Boolean)\n ),\n allowedFontFamilyKeys: new Set(\n fontFamilyKeys.map((s) => s.trim()).filter(Boolean)\n ),\n };\n }\n\n // Fallback: harvest backticked utilities from markdown.\n const backticked: string[] = [];\n for (const m of tailwindSection.matchAll(/`([^`]+)`/g)) {\n backticked.push(m[1]);\n }\n\n return {\n ...empty,\n allowedUtilities: new Set(\n backticked\n .flatMap((s) => s.split(/[,\\s]+/g))\n .map((s) => s.trim())\n .filter(Boolean)\n ),\n };\n}\n\nfunction tryParseFirstJsonCodeBlock(section: string): unknown | null {\n // Prefer ```json fenced blocks, but fall back to any fenced block.\n const jsonBlocks = [...section.matchAll(/```json\\s*([\\s\\S]*?)```/gi)];\n const anyBlocks = [...section.matchAll(/```\\s*([\\s\\S]*?)```/g)];\n\n const candidates = (jsonBlocks.length ? jsonBlocks : anyBlocks).map(\n (m) => m[1]\n );\n for (const raw of candidates) {\n const trimmed = raw.trim();\n if (!trimmed) continue;\n try {\n return JSON.parse(trimmed);\n } catch {\n continue;\n }\n }\n return null;\n}\n","/**\n * Generate Markdown style guides from extracted styles\n */\n\nimport type {\n ExtractedStyles,\n StyleGuide,\n TailwindThemeTokens,\n} from \"../types.js\";\nimport {\n extractClassTokensFromHtml,\n topEntries,\n} from \"../tailwind/class-tokens.js\";\n\n/**\n * Generates a Markdown style guide from extracted styles\n */\nexport interface GenerateStyleGuideOptions {\n /**\n * Optional HTML/TSX-ish string used to extract utility classes (Tailwind etc).\n */\n html?: string;\n /**\n * Optional Tailwind theme tokens (typically from tailwind.config.*).\n */\n tailwindTheme?: TailwindThemeTokens | null;\n}\n\nexport function generateStyleGuideFromStyles(\n styles: ExtractedStyles,\n options: GenerateStyleGuideOptions = {}\n): string {\n // NOTE: Style guide auto-generation has been removed.\n // UILint now requires an explicit, user-owned style guide file (typically\n // `.uilint/styleguide.md`) to avoid silently producing/overwriting rules.\n void styles;\n void options;\n throw new Error(\n 'Style guide auto-generation has been removed. Create \".uilint/styleguide.md\" at your workspace root (recommended: run \"/genstyleguide\" in Cursor).'\n );\n}\n\n/**\n * Finds the greatest common divisor of an array of numbers\n */\nfunction findGCD(numbers: number[]): number {\n if (numbers.length === 0) return 0;\n if (numbers.length === 1) return numbers[0];\n\n const gcd = (a: number, b: number): number => {\n a = Math.abs(Math.round(a));\n b = Math.abs(Math.round(b));\n while (b) {\n const t = b;\n b = a % b;\n a = t;\n }\n return a;\n };\n\n return numbers.reduce((acc, n) => gcd(acc, n));\n}\n\n/**\n * Converts a StyleGuide object back to Markdown\n */\nexport function styleGuideToMarkdown(guide: StyleGuide): string {\n const lines: string[] = [];\n\n lines.push(\"# UI Style Guide\");\n lines.push(\"\");\n\n // Colors\n lines.push(\"## Colors\");\n guide.colors.forEach((color) => {\n const usage = color.usage ? ` (${color.usage})` : \"\";\n lines.push(`- **${color.name}**: ${color.value}${usage}`);\n });\n lines.push(\"\");\n\n // Typography\n lines.push(\"## Typography\");\n guide.typography.forEach((typo) => {\n const props: string[] = [];\n if (typo.fontFamily) props.push(`font-family: \"${typo.fontFamily}\"`);\n if (typo.fontSize) props.push(`font-size: ${typo.fontSize}`);\n if (typo.fontWeight) props.push(`font-weight: ${typo.fontWeight}`);\n if (typo.lineHeight) props.push(`line-height: ${typo.lineHeight}`);\n lines.push(`- **${typo.element}**: ${props.join(\", \")}`);\n });\n lines.push(\"\");\n\n // Spacing\n lines.push(\"## Spacing\");\n guide.spacing.forEach((space) => {\n lines.push(`- **${space.name}**: ${space.value}`);\n });\n lines.push(\"\");\n\n // Components\n lines.push(\"## Components\");\n guide.components.forEach((comp) => {\n lines.push(`- **${comp.name}**: ${comp.styles.join(\", \")}`);\n });\n\n return lines.join(\"\\n\");\n}\n","/**\n * LLM prompt builders for UI consistency analysis\n */\n\nimport type {\n ElementSnapshot,\n GroupedSnapshot,\n ElementRole,\n} from \"./types.js\";\n\n/**\n * Formats an element for inclusion in the prompt (minimal data for LLM)\n */\nfunction formatElement(el: ElementSnapshot): string {\n const parts = [\n `id: ${el.id}`,\n `text: \"${el.text}\"`,\n el.component ? `component: ${el.component}` : null,\n `context: ${el.context || \"root\"}`,\n ].filter(Boolean);\n\n // Only include non-empty style values\n const styleEntries = Object.entries(el.styles).filter(\n ([, v]) => v && v !== \"0px\" && v !== \"none\" && v !== \"normal\"\n );\n if (styleEntries.length > 0) {\n const styleStr = styleEntries.map(([k, v]) => `${k}: ${v}`).join(\", \");\n parts.push(`styles: { ${styleStr} }`);\n }\n\n if (el.rect.width > 0 || el.rect.height > 0) {\n parts.push(`size: ${Math.round(el.rect.width)}x${Math.round(el.rect.height)}`);\n }\n\n return ` { ${parts.join(\", \")} }`;\n}\n\n/**\n * Formats a group of elements for the prompt\n */\nfunction formatGroup(\n groupName: string,\n elements: ElementSnapshot[]\n): string | null {\n if (elements.length < 2) return null;\n\n const lines = [`## ${groupName} (${elements.length} elements)`];\n elements.forEach((el) => {\n lines.push(formatElement(el));\n });\n return lines.join(\"\\n\");\n}\n\n/**\n * Builds a single prompt for analyzing ALL element groups\n * This reduces LLM calls to 1 for better performance\n */\nexport function buildConsistencyPrompt(snapshot: GroupedSnapshot): string {\n const groupSections: string[] = [];\n\n // Format each group that has 2+ elements\n const groups: Array<{ name: string; key: keyof GroupedSnapshot }> = [\n { name: \"Buttons\", key: \"buttons\" },\n { name: \"Headings\", key: \"headings\" },\n { name: \"Cards\", key: \"cards\" },\n { name: \"Links\", key: \"links\" },\n { name: \"Inputs\", key: \"inputs\" },\n { name: \"Containers\", key: \"containers\" },\n ];\n\n for (const { name, key } of groups) {\n const section = formatGroup(name, snapshot[key]);\n if (section) {\n groupSections.push(section);\n }\n }\n\n if (groupSections.length === 0) {\n return \"No element groups with 2+ elements found for consistency analysis.\";\n }\n\n return `You are a UI consistency analyzer. Your task is to find visual inconsistencies between similar UI elements that SHOULD match but DON'T.\n\n# Instructions\n\nAnalyze the following groups of UI elements. Within each group, elements should generally have consistent styling unless they're intentionally different (e.g., primary vs secondary buttons).\n\n## What to FLAG (violations):\n- Padding/spacing differences between similar elements (e.g., one button has 12px padding, another has 16px)\n- Font size or weight inconsistencies within same element types\n- Unintentional color variations (e.g., slightly different blues: #3B82F6 vs #3575E2)\n- Border radius mismatches (e.g., one card has 8px radius, another has 12px)\n- Shadow inconsistencies between similar components\n- Heading hierarchy issues (h1 should be larger than h2, h2 larger than h3)\n\n## What to NOT FLAG:\n- Intentional variations (primary vs secondary buttons, different heading levels)\n- Elements in different contexts that reasonably differ (header vs footer)\n- Sub-2px differences (likely rounding or subpixel rendering)\n- Different element types that shouldn't match\n\n# Element Groups\n\n${groupSections.join(\"\\n\\n\")}\n\n# Response Format\n\nRespond with JSON ONLY. Return a single JSON object with a \"violations\" array.\n\nEach violation should have:\n- elementIds: array of element IDs involved, e.g. [\"el-3\", \"el-7\"]\n- category: one of \"spacing\", \"color\", \"typography\", \"sizing\", \"borders\", \"shadows\"\n- severity: one of \"error\" (major inconsistency), \"warning\" (minor but noticeable), \"info\" (subtle)\n- message: short human-readable description\n- details: { property: the CSS property, values: array of differing values found, suggestion?: optional fix }\n\nExample response:\n{\n \"violations\": [\n {\n \"elementIds\": [\"el-3\", \"el-7\"],\n \"category\": \"spacing\",\n \"severity\": \"warning\",\n \"message\": \"Inconsistent padding on buttons\",\n \"details\": {\n \"property\": \"padding\",\n \"values\": [\"12px 24px\", \"16px 32px\"],\n \"suggestion\": \"Use consistent padding of 12px 24px\"\n }\n }\n ]\n}\n\nBe minimal. Only report significant inconsistencies. If no violations found, return {\"violations\": []}.`;\n}\n\n/**\n * Counts total elements across all groups\n */\nexport function countElements(snapshot: GroupedSnapshot): number {\n return (\n snapshot.buttons.length +\n snapshot.headings.length +\n snapshot.cards.length +\n snapshot.links.length +\n snapshot.inputs.length +\n snapshot.containers.length\n );\n}\n\n/**\n * Checks if a snapshot has any groups worth analyzing (2+ elements)\n */\nexport function hasAnalyzableGroups(snapshot: GroupedSnapshot): boolean {\n return (\n snapshot.buttons.length >= 2 ||\n snapshot.headings.length >= 2 ||\n snapshot.cards.length >= 2 ||\n snapshot.links.length >= 2 ||\n snapshot.inputs.length >= 2 ||\n snapshot.containers.length >= 2\n );\n}\n","/**\n * Consistency analysis logic - shared between CLI and API routes\n */\n\nimport type {\n GroupedSnapshot,\n Violation,\n ConsistencyResult,\n ViolationCategory,\n ViolationSeverity,\n} from \"./types.js\";\nimport { buildConsistencyPrompt, countElements, hasAnalyzableGroups } from \"./prompts.js\";\nimport { OllamaClient } from \"../ollama/client.js\";\n\nconst VALID_CATEGORIES: ViolationCategory[] = [\n \"spacing\",\n \"color\",\n \"typography\",\n \"sizing\",\n \"borders\",\n \"shadows\",\n];\n\nconst VALID_SEVERITIES: ViolationSeverity[] = [\"error\", \"warning\", \"info\"];\n\n/**\n * Parses and validates a GroupedSnapshot from JSON string\n */\nexport function parseGroupedSnapshot(json: string): GroupedSnapshot | null {\n try {\n const parsed = JSON.parse(json);\n\n // Validate structure\n if (!parsed || typeof parsed !== \"object\") return null;\n\n const result: GroupedSnapshot = {\n buttons: Array.isArray(parsed.buttons) ? parsed.buttons : [],\n headings: Array.isArray(parsed.headings) ? parsed.headings : [],\n cards: Array.isArray(parsed.cards) ? parsed.cards : [],\n links: Array.isArray(parsed.links) ? parsed.links : [],\n inputs: Array.isArray(parsed.inputs) ? parsed.inputs : [],\n containers: Array.isArray(parsed.containers) ? parsed.containers : [],\n };\n\n return result;\n } catch {\n return null;\n }\n}\n\n/**\n * Parses violations from LLM response with defensive handling\n */\nexport function parseViolationsResponse(response: string): Violation[] {\n try {\n // Try direct JSON parse first\n const parsed = JSON.parse(response);\n if (Array.isArray(parsed.violations)) {\n return validateViolations(parsed.violations);\n }\n return [];\n } catch {\n // Try to extract JSON from response using regex\n const jsonMatch = response.match(/\\{[\\s\\S]*\"violations\"[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const parsed = JSON.parse(jsonMatch[0]);\n if (Array.isArray(parsed.violations)) {\n return validateViolations(parsed.violations);\n }\n } catch {\n // Fallback failed\n }\n }\n return [];\n }\n}\n\n/**\n * Validates and filters violations to ensure correct structure\n */\nexport function validateViolations(violations: unknown[]): Violation[] {\n return violations\n .filter((v): v is Violation => {\n if (!v || typeof v !== \"object\") return false;\n const obj = v as Record<string, unknown>;\n\n // Required fields\n if (!Array.isArray(obj.elementIds)) return false;\n if (typeof obj.category !== \"string\") return false;\n if (typeof obj.severity !== \"string\") return false;\n if (typeof obj.message !== \"string\") return false;\n if (!obj.details || typeof obj.details !== \"object\") return false;\n\n // Validate category\n if (!VALID_CATEGORIES.includes(obj.category as ViolationCategory)) return false;\n\n // Validate severity\n if (!VALID_SEVERITIES.includes(obj.severity as ViolationSeverity)) return false;\n\n return true;\n })\n .map((v) => ({\n elementIds: v.elementIds,\n category: v.category,\n severity: v.severity,\n message: v.message,\n details: {\n property:\n typeof v.details.property === \"string\" ? v.details.property : \"\",\n values: Array.isArray(v.details.values) ? v.details.values : [],\n suggestion:\n typeof v.details.suggestion === \"string\"\n ? v.details.suggestion\n : undefined,\n },\n }));\n}\n\nexport interface AnalyzeConsistencyOptions {\n /** Ollama model to use */\n model?: string;\n /** Ollama base URL */\n baseUrl?: string;\n}\n\n/**\n * Analyzes a grouped snapshot for UI consistency violations\n * This is the main entry point for consistency analysis\n */\nexport async function analyzeConsistency(\n snapshot: GroupedSnapshot,\n options: AnalyzeConsistencyOptions = {}\n): Promise<ConsistencyResult> {\n const startTime = Date.now();\n const elementCount = countElements(snapshot);\n\n // Check if there are analyzable groups\n if (!hasAnalyzableGroups(snapshot)) {\n return {\n violations: [],\n elementCount,\n analysisTime: Date.now() - startTime,\n };\n }\n\n // Build prompt and call LLM\n const prompt = buildConsistencyPrompt(snapshot);\n const client = new OllamaClient({\n model: options.model,\n baseUrl: options.baseUrl,\n });\n\n const response = await client.complete(prompt, { json: true });\n\n // Parse violations\n const violations = parseViolationsResponse(response);\n\n return {\n violations,\n elementCount,\n analysisTime: Date.now() - startTime,\n };\n}\n\n/**\n * Formats violations for plain text output\n */\nexport function formatConsistencyViolations(violations: Violation[]): string {\n if (violations.length === 0) {\n return \"✓ No consistency issues found.\";\n }\n\n const lines: string[] = [`Found ${violations.length} consistency issue(s):\\n`];\n\n const severityIcons: Record<ViolationSeverity, string> = {\n error: \"✖\",\n warning: \"⚠\",\n info: \"ℹ\",\n };\n\n violations.forEach((v, i) => {\n const icon = severityIcons[v.severity] || \"•\";\n\n lines.push(`${i + 1}. ${icon} [${v.category}] ${v.message}`);\n lines.push(` Elements: ${v.elementIds.join(\", \")}`);\n\n if (v.details.property) {\n lines.push(` Property: ${v.details.property}`);\n }\n if (v.details.values.length > 0) {\n lines.push(` Values: ${v.details.values.join(\" vs \")}`);\n }\n if (v.details.suggestion) {\n lines.push(` Suggestion: ${v.details.suggestion}`);\n }\n lines.push(\"\");\n });\n\n return lines.join(\"\\n\");\n}\n\n"],"mappings":"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","names":[]}
|