@higinio/design-mcp 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +4 -4
- package/README.md +5 -5
- package/assets/bruma/design-doc.md +1083 -0
- package/assets/bruma/framework.css +1374 -0
- package/assets/bruma/styleguide.html +4687 -0
- package/assets/bruma/tailwind.css +121 -0
- package/assets/bruma/tokens.css +147 -0
- package/assets/bruma/tokens.json +995 -0
- package/assets/celeste/design-doc.md +56 -5
- package/assets/celeste/framework.css +93 -4
- package/assets/celeste/styleguide.html +252 -19
- package/assets/nocturna/design-doc.md +63 -12
- package/assets/nocturna/framework.css +102 -7
- package/assets/nocturna/styleguide.html +341 -118
- package/assets/nocturna/tokens.css +8 -3
- package/assets/salvia/design-doc.md +1067 -0
- package/assets/salvia/framework.css +1370 -0
- package/assets/salvia/styleguide.html +4670 -0
- package/assets/salvia/tailwind.css +121 -0
- package/assets/salvia/tokens.css +143 -0
- package/assets/salvia/tokens.json +1023 -0
- package/dist/cli.js +7 -5
- package/dist/doc.js +18 -4
- package/dist/preflight.js +2 -1
- package/dist/server.js +7 -7
- package/dist/systems.js +27 -1
- package/dist/validate.js +13 -5
- package/package.json +9 -31
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* design-lint — CLI de los validadores puros del MCP (validate_code, validate_motion,
|
|
4
4
|
* check_slop, checklist_preflight) para correr sobre archivos locales (pre-commit, CI).
|
|
5
5
|
*
|
|
6
|
-
* Uso: design-lint --system
|
|
6
|
+
* Uso: design-lint --system <id> [--lang css|html|js] [--json] <archivos o globs...>
|
|
7
|
+
* (la lista de ids la imprime USAGE).
|
|
7
8
|
*
|
|
8
9
|
* Exit codes: 0 = sin hallazgos de severidad alta · 1 = hay al menos un hallazgo de
|
|
9
10
|
* severidad alta (level "error") · 2 = error de uso (argumentos inválidos, sin archivos).
|
|
@@ -21,6 +22,7 @@ import { validateCode } from "./validate.js";
|
|
|
21
22
|
import { auditMotion } from "./motion.js";
|
|
22
23
|
import { checkSlop } from "./slop.js";
|
|
23
24
|
import { checkPreflight } from "./preflight.js";
|
|
25
|
+
import { SYSTEM_IDS } from "./systems.js";
|
|
24
26
|
const LANG_BY_EXT = {
|
|
25
27
|
".css": "css",
|
|
26
28
|
".html": "html",
|
|
@@ -190,7 +192,7 @@ export function formatText(summary) {
|
|
|
190
192
|
`en ${n} archivo${n === 1 ? "" : "s"} (sistema: ${summary.system}).`);
|
|
191
193
|
return lines.join("\n");
|
|
192
194
|
}
|
|
193
|
-
const USAGE = `Uso: design-lint --system
|
|
195
|
+
const USAGE = `Uso: design-lint --system ${SYSTEM_IDS.join("|")} [--lang css|html|js] [--json] <archivos o globs...>
|
|
194
196
|
|
|
195
197
|
Corre validate_code, validate_motion, check_slop y checklist_preflight sobre cada archivo.
|
|
196
198
|
El lenguaje se infiere por extensión (.css / .html|.htm / .js|.mjs|.cjs|.ts|.jsx|.tsx) salvo que se pase --lang.
|
|
@@ -217,9 +219,9 @@ export function parseArgs(argv) {
|
|
|
217
219
|
}
|
|
218
220
|
if (!opts.help) {
|
|
219
221
|
if (!opts.system)
|
|
220
|
-
opts.errors.push(
|
|
221
|
-
else if (
|
|
222
|
-
opts.errors.push(`Sistema desconocido: ${opts.system} (usa
|
|
222
|
+
opts.errors.push(`Falta --system (${SYSTEM_IDS.join("|")}).`);
|
|
223
|
+
else if (!SYSTEM_IDS.includes(opts.system))
|
|
224
|
+
opts.errors.push(`Sistema desconocido: ${opts.system} (usa ${SYSTEM_IDS.join(" o ")}).`);
|
|
223
225
|
if (opts.lang && !["css", "html", "js"].includes(opts.lang))
|
|
224
226
|
opts.errors.push(`Lenguaje desconocido: ${opts.lang} (usa css, html o js).`);
|
|
225
227
|
if (opts.patterns.length === 0)
|
package/dist/doc.js
CHANGED
|
@@ -43,10 +43,24 @@ export function listComponents(sections) {
|
|
|
43
43
|
}
|
|
44
44
|
return out;
|
|
45
45
|
}
|
|
46
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Devuelve la sección de un componente por slug exacto o por inclusión. El `body` incluye sus
|
|
48
|
+
* subsecciones (encabezados de nivel mayor que la siguen), con su línea de encabezado, hasta el
|
|
49
|
+
* siguiente encabezado de nivel igual o menor.
|
|
50
|
+
*/
|
|
47
51
|
export function getComponentSpec(sections, name) {
|
|
48
52
|
const target = slugify(name);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
let idx = sections.findIndex((s) => s.slug === target);
|
|
54
|
+
if (idx === -1)
|
|
55
|
+
idx = sections.findIndex((s) => s.slug.includes(target));
|
|
56
|
+
if (idx === -1)
|
|
57
|
+
return null;
|
|
58
|
+
const found = sections[idx];
|
|
59
|
+
const parts = [found.body];
|
|
60
|
+
for (const sub of sections.slice(idx + 1)) {
|
|
61
|
+
if (sub.level <= found.level)
|
|
62
|
+
break;
|
|
63
|
+
parts.push(`${"#".repeat(sub.level)} ${sub.heading}\n${sub.body}`);
|
|
64
|
+
}
|
|
65
|
+
return { ...found, body: parts.filter(Boolean).join("\n\n") };
|
|
52
66
|
}
|
package/dist/preflight.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SYSTEMS, SYSTEM_IDS } from "./systems.js";
|
|
1
2
|
/** Verifica estáticamente la estructura de un documento HTML/CSS antes de producción (SEO, a11y, perf estático). */
|
|
2
3
|
export function checkPreflight(code, lang) {
|
|
3
4
|
const out = [];
|
|
@@ -43,7 +44,7 @@ export function checkPreflight(code, lang) {
|
|
|
43
44
|
if (!/<meta\b[^>]*name\s*=\s*["']theme-color["']/i.test(code)) {
|
|
44
45
|
out.push({ level: "info", rule: "sin-theme-color", line: 0,
|
|
45
46
|
message: 'No hay <meta name="theme-color">.',
|
|
46
|
-
fix:
|
|
47
|
+
fix: `Añade theme-color con el fondo de la marca (${SYSTEM_IDS.map((id) => `${SYSTEMS[id].name} ${SYSTEMS[id].themeColor}`).join(", ")}); sin él la barra del navegador queda clara sobre una app oscura.` });
|
|
47
48
|
}
|
|
48
49
|
for (const meta of code.match(/<meta\b[^>]*>/gi) ?? []) {
|
|
49
50
|
if (/name\s*=\s*["']robots["']/i.test(meta) && /noindex/i.test(meta)) {
|
package/dist/server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { SYSTEM_IDS } from "./systems.js";
|
|
3
|
+
import { SYSTEMS, SYSTEM_IDS } from "./systems.js";
|
|
4
4
|
import { resolveToken, searchTokens } from "./tokens.js";
|
|
5
5
|
import { listComponents, getComponentSpec } from "./doc.js";
|
|
6
6
|
import { validateCode } from "./validate.js";
|
|
@@ -8,13 +8,13 @@ import { generateSnippet } from "./snippet.js";
|
|
|
8
8
|
import { auditMotion } from "./motion.js";
|
|
9
9
|
import { checkSlop } from "./slop.js";
|
|
10
10
|
import { checkPreflight } from "./preflight.js";
|
|
11
|
-
const systemEnum = z.enum(
|
|
11
|
+
const systemEnum = z.enum(SYSTEM_IDS);
|
|
12
12
|
function text(payload) {
|
|
13
13
|
const body = typeof payload === "string" ? payload : JSON.stringify(payload, null, 2);
|
|
14
14
|
return { content: [{ type: "text", text: body }] };
|
|
15
15
|
}
|
|
16
16
|
export function buildServer(store) {
|
|
17
|
-
const server = new McpServer({ name: "design-mcp", version: "0.
|
|
17
|
+
const server = new McpServer({ name: "design-mcp", version: "0.2.0" });
|
|
18
18
|
server.registerTool("list_design_systems", {
|
|
19
19
|
description: "Lista los sistemas de diseño disponibles con su metadata (filosofía, paleta, fuentes).",
|
|
20
20
|
inputSchema: {},
|
|
@@ -177,7 +177,7 @@ export function buildServer(store) {
|
|
|
177
177
|
});
|
|
178
178
|
server.registerPrompt("recomendar_direccion", {
|
|
179
179
|
title: "Recomendar dirección de diseño desde un brief",
|
|
180
|
-
description: "Dado un brief de producto, recomienda el sistema (
|
|
180
|
+
description: "Dado un brief de producto, recomienda el sistema (entre los sistemas registrados), los valores de dial (variación/motion/densidad) y una dirección de diseño sobria.",
|
|
181
181
|
argsSchema: { brief: z.string() },
|
|
182
182
|
}, ({ brief }) => ({
|
|
183
183
|
messages: [
|
|
@@ -186,8 +186,8 @@ export function buildServer(store) {
|
|
|
186
186
|
content: {
|
|
187
187
|
type: "text",
|
|
188
188
|
text: `Brief: "${brief}".\n\n` +
|
|
189
|
-
`Recomienda una dirección de diseño para uno de los
|
|
190
|
-
`- Elige
|
|
189
|
+
`Recomienda una dirección de diseño para uno de los sistemas de Higinio y justifícalo:\n` +
|
|
190
|
+
`- Elige ${SYSTEM_IDS.map((id) => `"${SYSTEMS[id].name}" (${SYSTEMS[id].pitch})`).join(", ")}, en una línea con el porqué.\n` +
|
|
191
191
|
`- Propón valores de dial acotados a la sobriedad de la marca: VARIANCE 1-6 (1=simétrico, 6=asimetría contenida), MOTION 1-5 (1=estático, 5=reveals sobrios), DENSITY por superficie (aireada en landings, densa en dashboards).\n` +
|
|
192
192
|
`- Da 2-3 decisiones clave (tipografía, uso del acento ≤10%, layout), dentro de las reglas duras del sistema elegido.\n\n` +
|
|
193
193
|
`Ancla la recomendación con list_design_systems y get_rules. No propongas gradientes, glassmorphism ni fuentes genéricas (Inter/Roboto).`,
|
|
@@ -208,7 +208,7 @@ export function buildServer(store) {
|
|
|
208
208
|
text: `Datos: "${datos}".\n\n` +
|
|
209
209
|
`Recomienda cómo visualizarlos con la skill dataviz-marca de Higinio:\n` +
|
|
210
210
|
`- El tipo de gráfica según la función del dato (magnitud → barras; tiempo → línea/área; parte de un todo → barra apilada; relación → scatter; un solo número → stat tile). Si no es una gráfica, dilo. Para tareas más específicas (waterfall, funnel, heatmap, treemap, box plot, bullet en vez de gauge, waffle, banda de confianza, anomalías) la tabla ampliada —con umbral de volumen, grado de accesibilidad y fallback por tipo— vive en la skill dataviz-marca (references/paletas-graficas.md); no la repitas aquí.\n` +
|
|
211
|
-
`- Qué paleta del sistema activo usar: categórica (identidad), secuencial (magnitud, una hue claro→oscuro), divergente (polaridad, dos hues + gris al centro) o estado (con icono + etiqueta). Usa la paleta del sistema
|
|
211
|
+
`- Qué paleta del sistema activo usar: categórica (identidad), secuencial (magnitud, una hue claro→oscuro), divergente (polaridad, dos hues + gris al centro) o estado (con icono + etiqueta). Usa la paleta del sistema activo; no inventes hex.\n` +
|
|
212
212
|
`- El fallback de accesibilidad: leyenda + etiquetas directas, vista de tabla, y patrón/textura para daltonismo. La identidad nunca es solo por color.\n\n` +
|
|
213
213
|
`Nunca dual-axis, ni arcoíris, ni 3D. El texto lleva tokens de ink, no el color de la serie.`,
|
|
214
214
|
},
|
package/dist/systems.js
CHANGED
|
@@ -5,6 +5,8 @@ export const SYSTEMS = {
|
|
|
5
5
|
name: "Celeste Clásica",
|
|
6
6
|
mode: "light",
|
|
7
7
|
philosophy: "Light-first, sereno y profesional. Banca privada boutique, no startup ruidosa.",
|
|
8
|
+
pitch: "light-first, sereno, profesional",
|
|
9
|
+
themeColor: "#f7f8fa",
|
|
8
10
|
palette: ["#a0c0d4", "#7192ba", "#31343e"],
|
|
9
11
|
fonts: { display: "Fraunces", body: "Public Sans", mono: "IBM Plex Mono" },
|
|
10
12
|
},
|
|
@@ -14,8 +16,32 @@ export const SYSTEMS = {
|
|
|
14
16
|
name: "Nocturna Eléctrica",
|
|
15
17
|
mode: "dark",
|
|
16
18
|
philosophy: "Dark-first, técnico y de alto contraste.",
|
|
19
|
+
pitch: "dark-first, técnico, alto contraste",
|
|
20
|
+
themeColor: "#010203",
|
|
17
21
|
palette: ["#52baed", "#353d7c", "#010203"],
|
|
18
22
|
fonts: { display: "Space Grotesk", body: "Manrope", mono: "JetBrains Mono" },
|
|
19
23
|
},
|
|
24
|
+
salvia: {
|
|
25
|
+
id: "salvia",
|
|
26
|
+
dir: "salvia-serena",
|
|
27
|
+
name: "Salvia Serena",
|
|
28
|
+
mode: "light",
|
|
29
|
+
philosophy: "Light-first, natural y relajante. Lino al sol y hojas de salvia; nada urge.",
|
|
30
|
+
pitch: "light-first, natural, relajante",
|
|
31
|
+
themeColor: "#f3f2ec",
|
|
32
|
+
palette: ["#4f6b58", "#2f7d6a", "#2c3630"],
|
|
33
|
+
fonts: { display: "Lora", body: "Nunito Sans", mono: "Source Code Pro" },
|
|
34
|
+
},
|
|
35
|
+
bruma: {
|
|
36
|
+
id: "bruma",
|
|
37
|
+
dir: "bruma-lavanda",
|
|
38
|
+
name: "Bruma Lavanda",
|
|
39
|
+
mode: "light",
|
|
40
|
+
philosophy: "Light-first, en calma, ordenado y legible. Una mañana de niebla en orden; todo en su sitio.",
|
|
41
|
+
pitch: "light-first, en calma, ordenado y legible",
|
|
42
|
+
themeColor: "#f2f1f6",
|
|
43
|
+
palette: ["#5b578a", "#2f7a68", "#2c2b38"],
|
|
44
|
+
fonts: { display: "Bricolage Grotesque", body: "Atkinson Hyperlegible Next", mono: "Atkinson Hyperlegible Mono" },
|
|
45
|
+
},
|
|
20
46
|
};
|
|
21
|
-
export const SYSTEM_IDS = ["celeste", "nocturna"];
|
|
47
|
+
export const SYSTEM_IDS = ["celeste", "nocturna", "salvia", "bruma"];
|
package/dist/validate.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/** primary-300 de los sistemas que lo reservan para decoración: nunca como color de texto. */
|
|
2
|
+
const PRIMARY_300_NO_TEXTO = {
|
|
3
|
+
celeste: "#a0c0d4",
|
|
4
|
+
salvia: "#97b09e",
|
|
5
|
+
bruma: "#9d99c4",
|
|
6
|
+
};
|
|
1
7
|
function relLuminance(hex) {
|
|
2
8
|
const h = hex.replace("#", "");
|
|
3
9
|
const n = h.length === 3 ? h.split("").map((c) => c + c).join("") : h;
|
|
@@ -26,15 +32,17 @@ export function validateCode(sys, code, lang) {
|
|
|
26
32
|
const lines = code.split("\n");
|
|
27
33
|
const paletteHex = new Set(sys.tokens.filter((t) => t.type === "color").map((t) => t.value.toLowerCase()));
|
|
28
34
|
const aliasByHex = hexToAlias(sys);
|
|
35
|
+
const primary300 = PRIMARY_300_NO_TEXTO[sys.meta.id];
|
|
29
36
|
lines.forEach((line, i) => {
|
|
30
37
|
const ln = i + 1;
|
|
31
38
|
const lower = line.toLowerCase();
|
|
32
|
-
const primary300AsText =
|
|
39
|
+
const primary300AsText = primary300 !== undefined
|
|
40
|
+
&& new RegExp(`(?<![\\w-])color\\s*:\\s*${primary300}`).test(lower);
|
|
33
41
|
for (const hex of lower.match(/#[0-9a-f]{6}\b/g) ?? []) {
|
|
34
|
-
if (hex ===
|
|
42
|
+
if (hex === primary300 && primary300AsText) {
|
|
35
43
|
out.push({ level: "error", rule: "primary-300-no-texto", line: ln,
|
|
36
|
-
message:
|
|
37
|
-
fix:
|
|
44
|
+
message: `primary-300 (${primary300}) no debe usarse como color de texto.`,
|
|
45
|
+
fix: `Usa --text-primary o --link; reserva ${primary300} para decoración.` });
|
|
38
46
|
}
|
|
39
47
|
else if (aliasByHex.has(hex)) {
|
|
40
48
|
out.push({ level: "warn", rule: "hex-directo", line: ln,
|
|
@@ -84,7 +92,7 @@ export function validateCode(sys, code, lang) {
|
|
|
84
92
|
}
|
|
85
93
|
// Escala tipográfica derivada de los aliases --text-* en rem (mismo enfoque que drift-radio con
|
|
86
94
|
// --radius-*). Si los tokens no la expusieran, cae a {12,14,16,18,20,25,31,39,49}: la escala 1.25
|
|
87
|
-
// base 16 de §4.2, idéntica en
|
|
95
|
+
// base 16 de §4.2, idéntica en todos los sistemas. Solo se evalúan px; rem/em y 0 quedan exentos.
|
|
88
96
|
const allowedFontPx = new Set([0]);
|
|
89
97
|
for (const [alias, val] of sys.aliases) {
|
|
90
98
|
if (!/^--text-/.test(alias))
|
package/package.json
CHANGED
|
@@ -1,40 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@higinio/design-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server de los design systems de Higinio (Celeste Clásica y
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "MCP server de los design systems de Higinio (Celeste Clásica, Nocturna Eléctrica, Salvia Serena y Bruma Lavanda).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"author": "Higinio Sosa",
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/HiginioSosa/Diseno-Higinio.git",
|
|
11
|
-
"directory": "mcp"
|
|
12
|
-
},
|
|
8
|
+
"repository": { "type": "git", "url": "git+https://github.com/HiginioSosa/Diseno-Higinio.git", "directory": "mcp" },
|
|
13
9
|
"homepage": "https://github.com/HiginioSosa/Diseno-Higinio#readme",
|
|
14
|
-
"bugs": {
|
|
15
|
-
|
|
16
|
-
},
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"design-tokens",
|
|
21
|
-
"celeste-clasica",
|
|
22
|
-
"nocturna-electrica"
|
|
23
|
-
],
|
|
24
|
-
"bin": {
|
|
25
|
-
"design-mcp": "dist/index.js",
|
|
26
|
-
"design-lint": "dist/cli.js"
|
|
27
|
-
},
|
|
28
|
-
"files": [
|
|
29
|
-
"dist",
|
|
30
|
-
"assets"
|
|
31
|
-
],
|
|
32
|
-
"publishConfig": {
|
|
33
|
-
"access": "public"
|
|
34
|
-
},
|
|
35
|
-
"engines": {
|
|
36
|
-
"node": ">=20"
|
|
37
|
-
},
|
|
10
|
+
"bugs": { "url": "https://github.com/HiginioSosa/Diseno-Higinio/issues" },
|
|
11
|
+
"keywords": ["mcp", "design-system", "design-tokens", "celeste-clasica", "nocturna-electrica", "salvia-serena", "bruma-lavanda"],
|
|
12
|
+
"bin": { "design-mcp": "dist/index.js", "design-lint": "dist/cli.js" },
|
|
13
|
+
"files": ["dist", "assets"],
|
|
14
|
+
"publishConfig": { "access": "public" },
|
|
15
|
+
"engines": { "node": ">=20" },
|
|
38
16
|
"scripts": {
|
|
39
17
|
"sync-assets": "tsx scripts/sync-assets.ts",
|
|
40
18
|
"build": "tsc",
|