@decantr/mcp-server 1.0.0-beta.10 → 1.0.0-beta.12
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 +21 -0
- package/README.md +3 -5
- package/dist/bin.js +1 -1
- package/dist/{chunk-A5IEC7O2.js → chunk-YOFQYTEC.js} +83 -75
- package/dist/critique-DKY4NB2O.js +74 -0
- package/dist/index.js +1 -1
- package/package.json +10 -8
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Decantr AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -64,17 +64,16 @@ Add to your Windsurf MCP config (`~/.windsurf/mcp.json`):
|
|
|
64
64
|
| `decantr_create_essence` | Generate an Essence spec skeleton from a project description | `{ "description": "SaaS dashboard with analytics and billing", "framework": "react" }` |
|
|
65
65
|
| `decantr_read_essence` | Read the current `decantr.essence.json` from the working directory | `{}` or `{ "path": "./custom.essence.json" }` |
|
|
66
66
|
| `decantr_validate` | Validate an Essence file against the schema and guard rules | `{ "path": "./decantr.essence.json" }` |
|
|
67
|
-
| `decantr_search_registry` | Search the community registry for patterns, archetypes,
|
|
67
|
+
| `decantr_search_registry` | Search the community registry for patterns, archetypes, themes, and shells | `{ "query": "kanban", "type": "pattern" }` |
|
|
68
68
|
| `decantr_resolve_pattern` | Get full pattern details: layout spec, components, presets, code examples | `{ "id": "data-table", "preset": "product" }` |
|
|
69
69
|
| `decantr_resolve_archetype` | Get archetype details: default pages, layouts, features, suggested theme | `{ "id": "saas-dashboard" }` |
|
|
70
|
-
| `decantr_resolve_recipe` | Get recipe decoration rules: shell styles, spatial hints, visual effects | `{ "id": "auradecantism" }` |
|
|
71
70
|
| `decantr_resolve_blueprint` | Get a full app composition with page structure and personality traits | `{ "id": "ecommerce" }` |
|
|
72
71
|
| `decantr_suggest_patterns` | Given a page description, get ranked pattern suggestions | `{ "description": "dashboard with metrics and charts" }` |
|
|
73
72
|
| `decantr_check_drift` | Check if generated code violates the design intent in the Essence spec | `{ "page_id": "overview", "components_used": ["Card", "LineChart"], "theme_used": "auradecantism" }` |
|
|
74
73
|
|
|
75
74
|
## How It Works
|
|
76
75
|
|
|
77
|
-
An Essence spec (`decantr.essence.json`) captures your design intent -- archetype, theme, page structure, patterns, and guard rules -- in a single declarative file. The MCP server exposes this spec and the Decantr registry to your AI assistant, giving it concrete layout specs, component lists, and
|
|
76
|
+
An Essence spec (`decantr.essence.json`) captures your design intent -- archetype, theme, page structure, patterns, and guard rules -- in a single declarative file. The MCP server exposes this spec and the Decantr registry to your AI assistant, giving it concrete layout specs, component lists, and visual treatments instead of relying on the model's generic training data. The result is generated code that follows a coherent design system, and drift detection that catches deviations before they ship.
|
|
78
77
|
|
|
79
78
|
## Example Workflow
|
|
80
79
|
|
|
@@ -86,8 +85,7 @@ The AI assistant calls these tools behind the scenes:
|
|
|
86
85
|
2. `decantr_resolve_archetype` -- pulls default pages, layouts, and features for a SaaS dashboard
|
|
87
86
|
3. `decantr_suggest_patterns` -- recommends `kpi-grid`, `chart-grid`, `data-table`, and `form-sections` for the described pages
|
|
88
87
|
4. `decantr_resolve_pattern` -- fetches layout specs and component lists for each pattern
|
|
89
|
-
5. `
|
|
90
|
-
6. `decantr_check_drift` -- validates the generated code against the Essence spec before presenting it
|
|
88
|
+
5. `decantr_check_drift` -- validates the generated code against the Essence spec before presenting it
|
|
91
89
|
|
|
92
90
|
The AI now generates code with the right layout structure, correct components, and consistent styling -- not a generic guess.
|
|
93
91
|
|
package/dist/bin.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./chunk-
|
|
2
|
+
import "./chunk-YOFQYTEC.js";
|
|
@@ -893,12 +893,12 @@ var TOOLS = [
|
|
|
893
893
|
{
|
|
894
894
|
name: "decantr_search_registry",
|
|
895
895
|
title: "Search Registry",
|
|
896
|
-
description: "Search the Decantr community content registry for patterns, archetypes,
|
|
896
|
+
description: "Search the Decantr community content registry for patterns, archetypes, themes, and shells.",
|
|
897
897
|
inputSchema: {
|
|
898
898
|
type: "object",
|
|
899
899
|
properties: {
|
|
900
900
|
query: { type: "string", description: 'Search query (e.g. "kanban", "neon", "dashboard")' },
|
|
901
|
-
type: { type: "string", description: "Filter by type: pattern, archetype,
|
|
901
|
+
type: { type: "string", description: "Filter by type: pattern, archetype, theme, shell" }
|
|
902
902
|
},
|
|
903
903
|
required: ["query"]
|
|
904
904
|
},
|
|
@@ -935,22 +935,7 @@ var TOOLS = [
|
|
|
935
935
|
},
|
|
936
936
|
annotations: READ_ONLY_NETWORK
|
|
937
937
|
},
|
|
938
|
-
// 6.
|
|
939
|
-
{
|
|
940
|
-
name: "decantr_resolve_recipe",
|
|
941
|
-
title: "Resolve Recipe",
|
|
942
|
-
description: "Get recipe visual treatment overrides, decoration rules, shell styles, spatial hints, visual effects, and pattern preferences.",
|
|
943
|
-
inputSchema: {
|
|
944
|
-
type: "object",
|
|
945
|
-
properties: {
|
|
946
|
-
id: { type: "string", description: 'Recipe ID (e.g. "auradecantism")' },
|
|
947
|
-
namespace: { type: "string", description: 'Namespace (default: "@official")' }
|
|
948
|
-
},
|
|
949
|
-
required: ["id"]
|
|
950
|
-
},
|
|
951
|
-
annotations: READ_ONLY_NETWORK
|
|
952
|
-
},
|
|
953
|
-
// 7. decantr_resolve_blueprint — network
|
|
938
|
+
// 6. decantr_resolve_blueprint — network
|
|
954
939
|
{
|
|
955
940
|
name: "decantr_resolve_blueprint",
|
|
956
941
|
title: "Resolve Blueprint",
|
|
@@ -1075,7 +1060,7 @@ var TOOLS = [
|
|
|
1075
1060
|
{
|
|
1076
1061
|
name: "decantr_get_section_context",
|
|
1077
1062
|
title: "Get Section Context",
|
|
1078
|
-
description: "Get the self-contained context for a specific section of the project. Returns guard rules, theme tokens, visual treatments,
|
|
1063
|
+
description: "Get the self-contained context for a specific section of the project. Returns guard rules, theme tokens, visual treatments, pattern specs, zone context, and pages \u2014 everything an AI needs to work on that section.",
|
|
1079
1064
|
inputSchema: {
|
|
1080
1065
|
type: "object",
|
|
1081
1066
|
properties: {
|
|
@@ -1106,6 +1091,20 @@ var TOOLS = [
|
|
|
1106
1091
|
required: ["component"]
|
|
1107
1092
|
},
|
|
1108
1093
|
annotations: READ_ONLY
|
|
1094
|
+
},
|
|
1095
|
+
// 15. decantr_critique — local read
|
|
1096
|
+
{
|
|
1097
|
+
name: "decantr_critique",
|
|
1098
|
+
title: "Design Critique",
|
|
1099
|
+
description: "Evaluate generated code against the essence spec for visual quality. Returns a scorecard covering treatment usage, decorator coverage, personality alignment, motion, accessibility, and responsiveness.",
|
|
1100
|
+
inputSchema: {
|
|
1101
|
+
type: "object",
|
|
1102
|
+
properties: {
|
|
1103
|
+
file_path: { type: "string", description: "Path to the component file to critique" }
|
|
1104
|
+
},
|
|
1105
|
+
required: ["file_path"]
|
|
1106
|
+
},
|
|
1107
|
+
annotations: READ_ONLY
|
|
1109
1108
|
}
|
|
1110
1109
|
];
|
|
1111
1110
|
async function handleTool(name, args) {
|
|
@@ -1206,17 +1205,6 @@ async function handleTool(name, args) {
|
|
|
1206
1205
|
return { found: false, message: `Archetype "${args.id}" not found in ${namespace}.` };
|
|
1207
1206
|
}
|
|
1208
1207
|
}
|
|
1209
|
-
case "decantr_resolve_recipe": {
|
|
1210
|
-
const err = validateStringArg(args, "id");
|
|
1211
|
-
if (err) return { error: err };
|
|
1212
|
-
const namespace = args.namespace || "@official";
|
|
1213
|
-
try {
|
|
1214
|
-
const recipe = await apiClient.getRecipe(namespace, args.id);
|
|
1215
|
-
return { found: true, ...recipe };
|
|
1216
|
-
} catch {
|
|
1217
|
-
return { found: false, message: `Recipe "${args.id}" not found in ${namespace}.` };
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
1208
|
case "decantr_resolve_blueprint": {
|
|
1221
1209
|
const err = validateStringArg(args, "id");
|
|
1222
1210
|
if (err) return { error: err };
|
|
@@ -1224,14 +1212,14 @@ async function handleTool(name, args) {
|
|
|
1224
1212
|
try {
|
|
1225
1213
|
const blueprint = await apiClient.getBlueprint(namespace, args.id);
|
|
1226
1214
|
let topology = null;
|
|
1227
|
-
const composeEntries = blueprint.compose
|
|
1215
|
+
const composeEntries = blueprint.compose;
|
|
1228
1216
|
if (composeEntries && Array.isArray(composeEntries) && composeEntries.length > 0) {
|
|
1229
1217
|
const zoneInputs = [];
|
|
1230
1218
|
const archetypePromises = composeEntries.map(async (entry) => {
|
|
1231
1219
|
const arcId = typeof entry === "string" ? entry : entry.archetype;
|
|
1232
1220
|
try {
|
|
1233
1221
|
const arch = await apiClient.getContent("archetypes", namespace, arcId);
|
|
1234
|
-
const archData = arch
|
|
1222
|
+
const archData = arch;
|
|
1235
1223
|
const explicitRole = typeof entry === "object" ? entry.role : void 0;
|
|
1236
1224
|
zoneInputs.push({
|
|
1237
1225
|
archetypeId: arcId,
|
|
@@ -1278,50 +1266,72 @@ async function handleTool(name, args) {
|
|
|
1278
1266
|
namespace: "@official",
|
|
1279
1267
|
limit: 100
|
|
1280
1268
|
});
|
|
1281
|
-
const
|
|
1269
|
+
const prelimScores = [];
|
|
1282
1270
|
for (const p of patternsResponse.items) {
|
|
1283
|
-
const
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
...p.tags || []
|
|
1288
|
-
].join(" ").toLowerCase();
|
|
1271
|
+
const slug = p.slug || "";
|
|
1272
|
+
const name2 = p.name || slug;
|
|
1273
|
+
const description = p.description || "";
|
|
1274
|
+
const searchable = [name2, description].join(" ").toLowerCase();
|
|
1289
1275
|
let score = 0;
|
|
1290
1276
|
const words = desc.split(/\s+/);
|
|
1291
1277
|
for (const word of words) {
|
|
1292
1278
|
if (word.length < 3) continue;
|
|
1293
1279
|
if (searchable.includes(word)) score += 10;
|
|
1294
1280
|
}
|
|
1295
|
-
if (desc.includes("dashboard") && ["kpi-grid", "chart-grid", "data-table", "filter-bar"].includes(
|
|
1296
|
-
if (desc.includes("metric") &&
|
|
1297
|
-
if (desc.includes("chart") &&
|
|
1298
|
-
if (desc.includes("table") &&
|
|
1299
|
-
if (desc.includes("form") &&
|
|
1300
|
-
if (desc.includes("setting") &&
|
|
1301
|
-
if (desc.includes("landing") && ["hero", "cta-section", "card-grid"].includes(
|
|
1302
|
-
if (desc.includes("hero") &&
|
|
1303
|
-
if (desc.includes("ecommerce") && ["card-grid", "filter-bar", "detail-header"].includes(
|
|
1304
|
-
if (desc.includes("product") &&
|
|
1305
|
-
if (desc.includes("feed") &&
|
|
1306
|
-
if (desc.includes("filter") &&
|
|
1307
|
-
if (desc.includes("search") &&
|
|
1281
|
+
if (desc.includes("dashboard") && ["kpi-grid", "chart-grid", "data-table", "filter-bar"].includes(slug)) score += 20;
|
|
1282
|
+
if (desc.includes("metric") && slug === "kpi-grid") score += 15;
|
|
1283
|
+
if (desc.includes("chart") && slug === "chart-grid") score += 15;
|
|
1284
|
+
if (desc.includes("table") && slug === "data-table") score += 15;
|
|
1285
|
+
if (desc.includes("form") && slug === "form-sections") score += 15;
|
|
1286
|
+
if (desc.includes("setting") && slug === "form-sections") score += 15;
|
|
1287
|
+
if (desc.includes("landing") && ["hero", "cta-section", "card-grid"].includes(slug)) score += 20;
|
|
1288
|
+
if (desc.includes("hero") && slug === "hero") score += 20;
|
|
1289
|
+
if (desc.includes("ecommerce") && ["card-grid", "filter-bar", "detail-header"].includes(slug)) score += 15;
|
|
1290
|
+
if (desc.includes("product") && slug === "card-grid") score += 15;
|
|
1291
|
+
if (desc.includes("feed") && slug === "activity-feed") score += 15;
|
|
1292
|
+
if (desc.includes("filter") && slug === "filter-bar") score += 15;
|
|
1293
|
+
if (desc.includes("search") && slug === "filter-bar") score += 10;
|
|
1308
1294
|
if (score > 0) {
|
|
1309
|
-
|
|
1310
|
-
suggestions.push({
|
|
1311
|
-
id: p.id,
|
|
1312
|
-
score,
|
|
1313
|
-
name: p.name || p.id,
|
|
1314
|
-
description: p.description || "",
|
|
1315
|
-
components: p.components || [],
|
|
1316
|
-
layout: preset?.layout ? preset.layout.layout : "grid"
|
|
1317
|
-
});
|
|
1295
|
+
prelimScores.push({ slug, score, name: name2, description });
|
|
1318
1296
|
}
|
|
1319
1297
|
}
|
|
1298
|
+
prelimScores.sort((a, b) => b.score - a.score);
|
|
1299
|
+
const top10 = prelimScores.slice(0, 10);
|
|
1300
|
+
const suggestions = [];
|
|
1301
|
+
for (const candidate of top10) {
|
|
1302
|
+
let fullPattern = null;
|
|
1303
|
+
try {
|
|
1304
|
+
const fetched = await apiClient.getPattern("@official", candidate.slug);
|
|
1305
|
+
fullPattern = fetched;
|
|
1306
|
+
} catch {
|
|
1307
|
+
}
|
|
1308
|
+
let score = candidate.score;
|
|
1309
|
+
if (fullPattern) {
|
|
1310
|
+
const fullSearchable = [
|
|
1311
|
+
...fullPattern.components || [],
|
|
1312
|
+
...fullPattern.tags || []
|
|
1313
|
+
].join(" ").toLowerCase();
|
|
1314
|
+
const words = desc.split(/\s+/);
|
|
1315
|
+
for (const word of words) {
|
|
1316
|
+
if (word.length < 3) continue;
|
|
1317
|
+
if (fullSearchable.includes(word)) score += 10;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
const preset = fullPattern?.presets ? Object.values(fullPattern.presets)[0] : null;
|
|
1321
|
+
suggestions.push({
|
|
1322
|
+
id: candidate.slug,
|
|
1323
|
+
score,
|
|
1324
|
+
name: fullPattern?.name || candidate.name,
|
|
1325
|
+
description: fullPattern?.description || candidate.description,
|
|
1326
|
+
components: fullPattern?.components || [],
|
|
1327
|
+
layout: preset?.layout ? preset.layout.layout : "grid"
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1320
1330
|
suggestions.sort((a, b) => b.score - a.score);
|
|
1321
1331
|
return {
|
|
1322
1332
|
query: args.description,
|
|
1323
1333
|
suggestions: suggestions.slice(0, 5),
|
|
1324
|
-
total:
|
|
1334
|
+
total: prelimScores.length
|
|
1325
1335
|
};
|
|
1326
1336
|
} catch (e) {
|
|
1327
1337
|
return { error: `Could not fetch patterns: ${e.message}` };
|
|
@@ -1343,10 +1353,10 @@ async function handleTool(name, args) {
|
|
|
1343
1353
|
if (args.theme_used && typeof args.theme_used === "string") {
|
|
1344
1354
|
let expectedStyle;
|
|
1345
1355
|
if (isV32(essence)) {
|
|
1346
|
-
expectedStyle = essence.dna.theme.
|
|
1356
|
+
expectedStyle = essence.dna.theme.id;
|
|
1347
1357
|
} else {
|
|
1348
1358
|
const expectedTheme = essence.theme;
|
|
1349
|
-
expectedStyle = expectedTheme?.style;
|
|
1359
|
+
expectedStyle = expectedTheme?.id ?? expectedTheme?.style;
|
|
1350
1360
|
}
|
|
1351
1361
|
if (expectedStyle && args.theme_used !== expectedStyle) {
|
|
1352
1362
|
violations.push({
|
|
@@ -1504,9 +1514,8 @@ async function handleTool(name, args) {
|
|
|
1504
1514
|
version: "3.0.0",
|
|
1505
1515
|
dna: {
|
|
1506
1516
|
theme: {
|
|
1507
|
-
|
|
1517
|
+
id: "auradecantism",
|
|
1508
1518
|
mode: "dark",
|
|
1509
|
-
recipe: "auradecantism",
|
|
1510
1519
|
shape: "rounded"
|
|
1511
1520
|
},
|
|
1512
1521
|
spacing: {
|
|
@@ -1585,14 +1594,14 @@ async function handleTool(name, args) {
|
|
|
1585
1594
|
}
|
|
1586
1595
|
const hasDnaViolation = violations.some((v) => {
|
|
1587
1596
|
const rule = v.rule;
|
|
1588
|
-
return ["style", "
|
|
1597
|
+
return ["style", "density", "theme-mode", "accessibility", "theme-match"].includes(rule);
|
|
1589
1598
|
});
|
|
1590
1599
|
if (hasDnaViolation && resolution !== "reject" && resolution !== "defer" && !args.confirm_dna) {
|
|
1591
1600
|
return {
|
|
1592
1601
|
error: "DNA-layer violations detected. Set confirm_dna: true to accept changes to design axioms (theme, style, density, etc.).",
|
|
1593
1602
|
requires_confirmation: true,
|
|
1594
1603
|
dna_rules_affected: violations.filter(
|
|
1595
|
-
(v) => ["style", "
|
|
1604
|
+
(v) => ["style", "density", "theme-mode", "accessibility", "theme-match"].includes(v.rule)
|
|
1596
1605
|
).map((v) => v.rule)
|
|
1597
1606
|
};
|
|
1598
1607
|
}
|
|
@@ -1773,6 +1782,11 @@ async function handleTool(name, args) {
|
|
|
1773
1782
|
}
|
|
1774
1783
|
return { content: lines.join("\n") };
|
|
1775
1784
|
}
|
|
1785
|
+
case "decantr_critique": {
|
|
1786
|
+
const { critiqueFile } = await import("./critique-DKY4NB2O.js");
|
|
1787
|
+
const result = await critiqueFile(args.file_path, process.cwd());
|
|
1788
|
+
return result;
|
|
1789
|
+
}
|
|
1776
1790
|
default:
|
|
1777
1791
|
return { error: `Unknown tool: ${name}` };
|
|
1778
1792
|
}
|
|
@@ -1782,7 +1796,7 @@ function applyDriftAcceptance(essence, violation, resolution, scope) {
|
|
|
1782
1796
|
case "theme-match":
|
|
1783
1797
|
case "style": {
|
|
1784
1798
|
if (violation.details) {
|
|
1785
|
-
essence.dna.theme.
|
|
1799
|
+
essence.dna.theme.id = violation.details;
|
|
1786
1800
|
}
|
|
1787
1801
|
break;
|
|
1788
1802
|
}
|
|
@@ -1802,12 +1816,6 @@ function applyDriftAcceptance(essence, violation, resolution, scope) {
|
|
|
1802
1816
|
case "layout": {
|
|
1803
1817
|
break;
|
|
1804
1818
|
}
|
|
1805
|
-
case "recipe": {
|
|
1806
|
-
if (violation.details) {
|
|
1807
|
-
essence.dna.theme.recipe = violation.details;
|
|
1808
|
-
}
|
|
1809
|
-
break;
|
|
1810
|
-
}
|
|
1811
1819
|
case "density": {
|
|
1812
1820
|
break;
|
|
1813
1821
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// src/critique.ts
|
|
2
|
+
import { readFile } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
async function critiqueFile(filePath, projectRoot) {
|
|
5
|
+
const code = await readFile(filePath, "utf-8");
|
|
6
|
+
const codeLower = code.toLowerCase();
|
|
7
|
+
let treatments = "";
|
|
8
|
+
try {
|
|
9
|
+
treatments = await readFile(join(projectRoot, "src", "styles", "treatments.css"), "utf-8");
|
|
10
|
+
} catch {
|
|
11
|
+
}
|
|
12
|
+
const scores = [];
|
|
13
|
+
const treatmentClasses = ["d-interactive", "d-surface", "d-data", "d-control", "d-section", "d-annotation", "d-label"];
|
|
14
|
+
const usedTreatments = treatmentClasses.filter((t) => code.includes(t));
|
|
15
|
+
scores.push({
|
|
16
|
+
category: "Treatment Usage",
|
|
17
|
+
score: Math.min(5, Math.max(1, Math.round(usedTreatments.length / treatmentClasses.length * 5))),
|
|
18
|
+
details: `${usedTreatments.length}/${treatmentClasses.length} base treatments used: ${usedTreatments.join(", ") || "none"}`,
|
|
19
|
+
suggestions: treatmentClasses.filter((t) => !code.includes(t)).map((t) => `Consider using \`${t}\` where appropriate`)
|
|
20
|
+
});
|
|
21
|
+
const decoratorMatches = treatments.match(/\.([\w-]+)\s*\{/g) || [];
|
|
22
|
+
const decoratorNames = decoratorMatches.map((m) => m.slice(1).replace(/\s*\{$/, "")).filter((n) => !n.startsWith("d-") && !["neon-glow", "neon-glow-hover", "neon-text-glow", "neon-border-glow", "mono-data", "status-ring", "entrance-fade"].includes(n));
|
|
23
|
+
const usedDecorators = decoratorNames.filter((d) => code.includes(d));
|
|
24
|
+
const decTarget = Math.min(decoratorNames.length, 5);
|
|
25
|
+
scores.push({
|
|
26
|
+
category: "Decorator Usage",
|
|
27
|
+
score: decTarget > 0 ? Math.min(5, Math.max(1, Math.round(usedDecorators.length / decTarget * 5))) : 3,
|
|
28
|
+
details: `${usedDecorators.length}/${decoratorNames.length} theme decorators used`,
|
|
29
|
+
suggestions: decoratorNames.filter((d) => !code.includes(d)).slice(0, 3).map((d) => `Theme decorator \`${d}\` available but unused`)
|
|
30
|
+
});
|
|
31
|
+
const personalityUtils = ["neon-glow", "neon-text-glow", "neon-border-glow", "mono-data", "status-ring", "entrance-fade"];
|
|
32
|
+
const availableUtils = personalityUtils.filter((u) => treatments.includes(u));
|
|
33
|
+
const usedUtils = availableUtils.filter((u) => code.includes(u));
|
|
34
|
+
const utilTarget = Math.min(availableUtils.length, 3);
|
|
35
|
+
scores.push({
|
|
36
|
+
category: "Personality Alignment",
|
|
37
|
+
score: utilTarget > 0 ? Math.min(5, Math.max(1, Math.round(usedUtils.length / utilTarget * 5))) : 3,
|
|
38
|
+
details: `${usedUtils.length}/${availableUtils.length} personality utilities used`,
|
|
39
|
+
suggestions: availableUtils.filter((u) => !code.includes(u)).map((u) => `Personality utility \`${u}\` defined but not used`)
|
|
40
|
+
});
|
|
41
|
+
const hasTransition = codeLower.includes("transition") || codeLower.includes("animate") || codeLower.includes("keyframe") || codeLower.includes("framer");
|
|
42
|
+
const hasHover = codeLower.includes(":hover") || codeLower.includes("onmouseenter") || codeLower.includes("hover:");
|
|
43
|
+
scores.push({
|
|
44
|
+
category: "Motion & Interaction",
|
|
45
|
+
score: Math.min(5, (hasTransition ? 3 : 1) + (hasHover ? 1 : 0)),
|
|
46
|
+
details: `Transitions: ${hasTransition ? "yes" : "no"}, Hover states: ${hasHover ? "yes" : "no"}`,
|
|
47
|
+
suggestions: hasTransition ? [] : ["Add transition effects for interactive elements"]
|
|
48
|
+
});
|
|
49
|
+
const hasAria = codeLower.includes("aria-") || codeLower.includes("role=");
|
|
50
|
+
const hasFocus = codeLower.includes("focus-visible") || codeLower.includes("focusvisible");
|
|
51
|
+
const hasKeyboard = codeLower.includes("onkeydown") || codeLower.includes("onkeyup");
|
|
52
|
+
scores.push({
|
|
53
|
+
category: "Accessibility",
|
|
54
|
+
score: Math.min(5, 1 + (hasAria ? 2 : 0) + (hasFocus ? 1 : 0) + (hasKeyboard ? 1 : 0)),
|
|
55
|
+
details: `ARIA: ${hasAria}, Focus: ${hasFocus}, Keyboard: ${hasKeyboard}`,
|
|
56
|
+
suggestions: [
|
|
57
|
+
...!hasAria ? ["Add ARIA attributes to interactive regions"] : [],
|
|
58
|
+
...!hasKeyboard ? ["Add keyboard event handlers"] : []
|
|
59
|
+
]
|
|
60
|
+
});
|
|
61
|
+
const hasMedia = codeLower.includes("@media") || codeLower.includes("usemediaquery") || codeLower.includes("breakpoint");
|
|
62
|
+
const hasResponsive = codeLower.includes("sm:") || codeLower.includes("md:") || codeLower.includes("lg:") || codeLower.includes("_sm_") || codeLower.includes("_md_");
|
|
63
|
+
scores.push({
|
|
64
|
+
category: "Responsive Design",
|
|
65
|
+
score: Math.min(5, (hasMedia ? 3 : 1) + (hasResponsive ? 2 : 0)),
|
|
66
|
+
details: `Media queries: ${hasMedia}, Responsive classes: ${hasResponsive}`,
|
|
67
|
+
suggestions: !hasMedia && !hasResponsive ? ["Add responsive breakpoint handling"] : []
|
|
68
|
+
});
|
|
69
|
+
const overall = Math.round(scores.reduce((s, c) => s + c.score, 0) / scores.length * 10) / 10;
|
|
70
|
+
return { file: filePath, overall, scores };
|
|
71
|
+
}
|
|
72
|
+
export {
|
|
73
|
+
critiqueFile
|
|
74
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import "./chunk-
|
|
1
|
+
import "./chunk-YOFQYTEC.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decantr/mcp-server",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.12",
|
|
4
4
|
"description": "MCP server for Decantr — exposes design intelligence tools to AI coding assistants",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -15,18 +15,20 @@
|
|
|
15
15
|
},
|
|
16
16
|
"main": "dist/index.js",
|
|
17
17
|
"types": "dist/index.d.ts",
|
|
18
|
-
"files": [
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
19
21
|
"publishConfig": {
|
|
20
22
|
"access": "public"
|
|
21
23
|
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
26
|
+
"@decantr/essence-spec": "1.0.0-beta.11",
|
|
27
|
+
"@decantr/registry": "1.0.0-beta.11"
|
|
28
|
+
},
|
|
22
29
|
"scripts": {
|
|
23
30
|
"build": "tsup",
|
|
24
31
|
"test": "vitest run",
|
|
25
32
|
"test:watch": "vitest"
|
|
26
|
-
},
|
|
27
|
-
"dependencies": {
|
|
28
|
-
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
29
|
-
"@decantr/essence-spec": "workspace:*",
|
|
30
|
-
"@decantr/registry": "workspace:*"
|
|
31
33
|
}
|
|
32
|
-
}
|
|
34
|
+
}
|