@pure-ds/core 0.7.4 → 0.7.5
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/.cursorrules +21 -0
- package/.github/copilot-instructions.md +21 -0
- package/dist/types/src/js/pds-core/pds-start-helpers.d.ts.map +1 -1
- package/package.json +8 -2
- package/packages/pds-cli/bin/pds-bootstrap.js +1 -1
- package/packages/pds-cli/bin/pds-import.js +1 -1
- package/packages/pds-cli/bin/pds-mcp-eval.js +79 -0
- package/packages/pds-cli/bin/pds-mcp-health.js +128 -0
- package/packages/pds-cli/bin/pds-mcp-server.js +140 -0
- package/packages/pds-cli/bin/pds-setup-mcp.js +72 -0
- package/packages/pds-cli/lib/pds-mcp-core.js +497 -0
- package/packages/pds-cli/lib/pds-mcp-eval-cases.json +72 -0
- package/public/assets/js/app.js +1582 -4
- package/public/assets/js/app.js.map +7 -0
- package/public/assets/js/lit.js +1078 -3
- package/public/assets/js/lit.js.map +7 -0
- package/public/assets/js/pds-ask.js +239 -9
- package/public/assets/js/pds-ask.js.map +7 -0
- package/public/assets/js/pds-autocomplete.js +590 -7
- package/public/assets/js/pds-autocomplete.js.map +7 -0
- package/public/assets/js/pds-enhancers.js +689 -1
- package/public/assets/js/pds-enhancers.js.map +7 -0
- package/public/assets/js/pds-manager.js +15947 -3101
- package/public/assets/js/pds-manager.js.map +7 -0
- package/public/assets/js/pds-toast.js +30 -1
- package/public/assets/js/pds-toast.js.map +7 -0
- package/public/assets/js/pds.js +1442 -2
- package/public/assets/js/pds.js.map +7 -0
- package/public/assets/pds/core/pds-ask.js +239 -9
- package/public/assets/pds/core/pds-autocomplete.js +590 -7
- package/public/assets/pds/core/pds-enhancers.js +689 -1
- package/public/assets/pds/core/pds-manager.js +15947 -3101
- package/public/assets/pds/core/pds-toast.js +30 -1
- package/public/assets/pds/core.js +1442 -2
- package/public/assets/pds/external/lit.js +1078 -3
- package/readme.md +44 -2
- package/src/js/pds-core/pds-start-helpers.js +0 -20
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '../../..');
|
|
8
|
+
|
|
9
|
+
function isObject(value) {
|
|
10
|
+
return value && typeof value === 'object' && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalize(value) {
|
|
14
|
+
return String(value || '').toLowerCase();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function decodeDataTextUrl(url) {
|
|
18
|
+
if (typeof url !== 'string' || !url.startsWith('data:text/plain,')) return null;
|
|
19
|
+
try {
|
|
20
|
+
return decodeURIComponent(url.replace('data:text/plain,', ''));
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizePath(p) {
|
|
27
|
+
return p.split(path.sep).join('/');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function resolveSsoTRoot(projectRoot = process.cwd()) {
|
|
31
|
+
const candidates = [
|
|
32
|
+
PACKAGE_ROOT,
|
|
33
|
+
path.join(projectRoot, 'node_modules', '@pure-ds', 'core'),
|
|
34
|
+
projectRoot,
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
for (const candidate of candidates) {
|
|
38
|
+
const hasCustomElements = existsSync(path.join(candidate, 'custom-elements.json'));
|
|
39
|
+
const hasCssData = existsSync(path.join(candidate, 'public', 'assets', 'pds', 'pds.css-data.json'));
|
|
40
|
+
const hasCore = existsSync(path.join(candidate, 'src', 'js', 'pds-core'));
|
|
41
|
+
if (hasCustomElements && hasCssData && hasCore) {
|
|
42
|
+
return candidate;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return PACKAGE_ROOT;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getSelectorClasses(selector) {
|
|
50
|
+
const classes = [];
|
|
51
|
+
const regex = /\.([a-zA-Z][\w-]*)/g;
|
|
52
|
+
let match = regex.exec(selector);
|
|
53
|
+
while (match) {
|
|
54
|
+
classes.push(match[1]);
|
|
55
|
+
match = regex.exec(selector);
|
|
56
|
+
}
|
|
57
|
+
return classes;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function buildSuggestions(items, term, limit = 5) {
|
|
61
|
+
const needle = normalize(term);
|
|
62
|
+
if (!needle) return [];
|
|
63
|
+
const starts = [];
|
|
64
|
+
const contains = [];
|
|
65
|
+
for (const item of items) {
|
|
66
|
+
const n = normalize(item);
|
|
67
|
+
if (n.startsWith(needle)) starts.push(item);
|
|
68
|
+
else if (n.includes(needle)) contains.push(item);
|
|
69
|
+
}
|
|
70
|
+
return [...new Set([...starts, ...contains])].slice(0, limit);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getToolSchema() {
|
|
74
|
+
return [
|
|
75
|
+
{
|
|
76
|
+
name: 'get_tokens',
|
|
77
|
+
description: 'Search PDS CSS custom property tokens from pds.css-data.json.',
|
|
78
|
+
inputSchema: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {
|
|
81
|
+
contains: { type: 'string', description: 'Substring to search in token name/description.' },
|
|
82
|
+
prefix: { type: 'string', description: 'Token prefix filter, e.g. --color-, --spacing-.' },
|
|
83
|
+
limit: { type: 'number', minimum: 1, maximum: 200, default: 40 },
|
|
84
|
+
includeValues: { type: 'boolean', default: true },
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'find_utility_class',
|
|
90
|
+
description: 'Find PDS selectors/classes from ontology primitives/layout/utilities metadata.',
|
|
91
|
+
inputSchema: {
|
|
92
|
+
type: 'object',
|
|
93
|
+
properties: {
|
|
94
|
+
query: { type: 'string', description: 'Class or concept, e.g. gap, surface, btn, flex.' },
|
|
95
|
+
limit: { type: 'number', minimum: 1, maximum: 100, default: 30 },
|
|
96
|
+
section: {
|
|
97
|
+
type: 'string',
|
|
98
|
+
enum: ['primitives', 'components', 'layoutPatterns', 'all'],
|
|
99
|
+
default: 'all',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'get_component_api',
|
|
106
|
+
description: 'Lookup PDS custom element API from custom-elements.json.',
|
|
107
|
+
inputSchema: {
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: {
|
|
110
|
+
tagName: { type: 'string', description: 'Custom element tag, e.g. pds-form.' },
|
|
111
|
+
contains: { type: 'string', description: 'Search by tag/member/attribute/event text.' },
|
|
112
|
+
limit: { type: 'number', minimum: 1, maximum: 50, default: 10 },
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'get_enhancer_metadata',
|
|
118
|
+
description: 'Read enhancer selector descriptions and demoHtml from pds-enhancers-meta.js.',
|
|
119
|
+
inputSchema: {
|
|
120
|
+
type: 'object',
|
|
121
|
+
properties: {
|
|
122
|
+
selector: { type: 'string', description: 'Exact or partial enhancer selector.' },
|
|
123
|
+
contains: { type: 'string', description: 'Search text in selector/description/demoHtml.' },
|
|
124
|
+
includeDemoHtml: { type: 'boolean', default: true },
|
|
125
|
+
limit: { type: 'number', minimum: 1, maximum: 100, default: 20 },
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'get_config_relations',
|
|
131
|
+
description: 'Read PDS_CONFIG_RELATIONS from pds-config.js for deterministic token mapping.',
|
|
132
|
+
inputSchema: {
|
|
133
|
+
type: 'object',
|
|
134
|
+
properties: {
|
|
135
|
+
pathPrefix: { type: 'string', description: 'Config path prefix, e.g. colors, layout.' },
|
|
136
|
+
contains: { type: 'string', description: 'Search inside relation payload JSON.' },
|
|
137
|
+
limit: { type: 'number', minimum: 1, maximum: 200, default: 60 },
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'validate_pds_snippet',
|
|
143
|
+
description: 'Validate HTML snippet for unknown classes, tokens, and pds-* tags against SSoT.',
|
|
144
|
+
inputSchema: {
|
|
145
|
+
type: 'object',
|
|
146
|
+
required: ['html'],
|
|
147
|
+
properties: {
|
|
148
|
+
html: { type: 'string', description: 'HTML snippet to validate.' },
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function loadJson(filePath) {
|
|
156
|
+
const raw = await readFile(filePath, 'utf-8');
|
|
157
|
+
return JSON.parse(raw);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function importModule(filePath) {
|
|
161
|
+
return import(pathToFileURL(filePath).href);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function createPdsMcpContext({ projectRoot = process.cwd() } = {}) {
|
|
165
|
+
const ssoTRoot = resolveSsoTRoot(projectRoot);
|
|
166
|
+
return {
|
|
167
|
+
projectRoot,
|
|
168
|
+
ssoTRoot,
|
|
169
|
+
files: {
|
|
170
|
+
cssData: path.join(ssoTRoot, 'public', 'assets', 'pds', 'pds.css-data.json'),
|
|
171
|
+
customElements: path.join(ssoTRoot, 'custom-elements.json'),
|
|
172
|
+
ontology: path.join(ssoTRoot, 'src', 'js', 'pds-core', 'pds-ontology.js'),
|
|
173
|
+
enhancersMeta: path.join(ssoTRoot, 'src', 'js', 'pds-core', 'pds-enhancers-meta.js'),
|
|
174
|
+
config: path.join(ssoTRoot, 'src', 'js', 'pds-core', 'pds-config.js'),
|
|
175
|
+
},
|
|
176
|
+
cache: new Map(),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function getCssData(ctx) {
|
|
181
|
+
if (!ctx.cache.has('cssData')) {
|
|
182
|
+
ctx.cache.set('cssData', await loadJson(ctx.files.cssData));
|
|
183
|
+
}
|
|
184
|
+
return ctx.cache.get('cssData');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function getCustomElements(ctx) {
|
|
188
|
+
if (!ctx.cache.has('customElements')) {
|
|
189
|
+
ctx.cache.set('customElements', await loadJson(ctx.files.customElements));
|
|
190
|
+
}
|
|
191
|
+
return ctx.cache.get('customElements');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function getOntology(ctx) {
|
|
195
|
+
if (!ctx.cache.has('ontology')) {
|
|
196
|
+
const mod = await importModule(ctx.files.ontology);
|
|
197
|
+
ctx.cache.set('ontology', mod.ontology || {});
|
|
198
|
+
}
|
|
199
|
+
return ctx.cache.get('ontology');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function getEnhancerMeta(ctx) {
|
|
203
|
+
if (!ctx.cache.has('enhancersMeta')) {
|
|
204
|
+
const mod = await importModule(ctx.files.enhancersMeta);
|
|
205
|
+
ctx.cache.set('enhancersMeta', mod.defaultPDSEnhancerMetadata || []);
|
|
206
|
+
}
|
|
207
|
+
return ctx.cache.get('enhancersMeta');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function getConfigRelations(ctx) {
|
|
211
|
+
if (!ctx.cache.has('configRelations')) {
|
|
212
|
+
const mod = await importModule(ctx.files.config);
|
|
213
|
+
ctx.cache.set('configRelations', mod.PDS_CONFIG_RELATIONS || {});
|
|
214
|
+
}
|
|
215
|
+
return ctx.cache.get('configRelations');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function shapeComponentDeclaration(declaration) {
|
|
219
|
+
const pick = (items = [], mapper) => items.map(mapper).slice(0, 80);
|
|
220
|
+
return {
|
|
221
|
+
tagName: declaration.tagName,
|
|
222
|
+
className: declaration.name,
|
|
223
|
+
description: declaration.description || '',
|
|
224
|
+
attributes: pick(declaration.attributes, (a) => ({
|
|
225
|
+
name: a.name,
|
|
226
|
+
type: a?.type?.text || null,
|
|
227
|
+
description: a.description || '',
|
|
228
|
+
})),
|
|
229
|
+
events: pick(declaration.events, (e) => ({
|
|
230
|
+
name: e.name,
|
|
231
|
+
type: e?.type?.text || null,
|
|
232
|
+
description: e.description || '',
|
|
233
|
+
})),
|
|
234
|
+
cssParts: pick(declaration.cssParts, (p) => ({ name: p.name, description: p.description || '' })),
|
|
235
|
+
cssProperties: pick(declaration.cssProperties, (p) => ({ name: p.name, description: p.description || '' })),
|
|
236
|
+
members: pick(declaration.members, (m) => ({
|
|
237
|
+
name: m.name,
|
|
238
|
+
kind: m.kind,
|
|
239
|
+
type: m?.type?.text || null,
|
|
240
|
+
description: m.description || '',
|
|
241
|
+
})),
|
|
242
|
+
slots: pick(declaration.slots, (s) => ({ name: s.name || '', description: s.description || '' })),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function handleGetTokens(ctx, args = {}) {
|
|
247
|
+
const { contains = '', prefix = '', includeValues = true } = args;
|
|
248
|
+
const limit = Math.min(Math.max(Number(args.limit || 40), 1), 200);
|
|
249
|
+
const cssData = await getCssData(ctx);
|
|
250
|
+
const needle = normalize(contains);
|
|
251
|
+
const prefixNeedle = normalize(prefix);
|
|
252
|
+
|
|
253
|
+
const matches = (cssData.properties || [])
|
|
254
|
+
.filter((property) => {
|
|
255
|
+
const name = normalize(property.name);
|
|
256
|
+
const description = normalize(property.description);
|
|
257
|
+
const prefixOk = !prefixNeedle || name.startsWith(prefixNeedle);
|
|
258
|
+
const containsOk = !needle || name.includes(needle) || description.includes(needle);
|
|
259
|
+
return prefixOk && containsOk;
|
|
260
|
+
})
|
|
261
|
+
.slice(0, limit)
|
|
262
|
+
.map((property) => {
|
|
263
|
+
const value = includeValues
|
|
264
|
+
? decodeDataTextUrl(property?.references?.find((r) => r.name === 'Value')?.url)
|
|
265
|
+
: null;
|
|
266
|
+
return {
|
|
267
|
+
name: property.name,
|
|
268
|
+
description: property.description || '',
|
|
269
|
+
syntax: property.syntax || '',
|
|
270
|
+
value,
|
|
271
|
+
};
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
ssoTRoot: normalizePath(ctx.ssoTRoot),
|
|
276
|
+
source: normalizePath(path.relative(ctx.ssoTRoot, ctx.files.cssData)),
|
|
277
|
+
totalMatches: matches.length,
|
|
278
|
+
tokens: matches,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function handleFindUtilityClass(ctx, args = {}) {
|
|
283
|
+
const { query = '', section = 'all' } = args;
|
|
284
|
+
const limit = Math.min(Math.max(Number(args.limit || 30), 1), 100);
|
|
285
|
+
const ontology = await getOntology(ctx);
|
|
286
|
+
const needle = normalize(query);
|
|
287
|
+
|
|
288
|
+
const buckets = [];
|
|
289
|
+
if (section === 'all' || section === 'primitives') buckets.push(...(ontology.primitives || []));
|
|
290
|
+
if (section === 'all' || section === 'components') buckets.push(...(ontology.components || []));
|
|
291
|
+
if (section === 'all' || section === 'layoutPatterns') buckets.push(...(ontology.layoutPatterns || []));
|
|
292
|
+
|
|
293
|
+
const matches = [];
|
|
294
|
+
const classSet = new Set();
|
|
295
|
+
|
|
296
|
+
for (const item of buckets) {
|
|
297
|
+
const selectors = item.selectors || [];
|
|
298
|
+
for (const selector of selectors) {
|
|
299
|
+
const selectorText = String(selector);
|
|
300
|
+
const matchTarget = `${item.name || ''} ${item.description || ''} ${selectorText}`.toLowerCase();
|
|
301
|
+
if (needle && !matchTarget.includes(needle)) continue;
|
|
302
|
+
matches.push({
|
|
303
|
+
selector: selectorText,
|
|
304
|
+
name: item.name || '',
|
|
305
|
+
description: item.description || '',
|
|
306
|
+
category: item.category || '',
|
|
307
|
+
id: item.id || '',
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const classes = getSelectorClasses(selectorText);
|
|
311
|
+
for (const className of classes) classSet.add(className);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
ssoTRoot: normalizePath(ctx.ssoTRoot),
|
|
317
|
+
source: normalizePath(path.relative(ctx.ssoTRoot, ctx.files.ontology)),
|
|
318
|
+
totalMatches: matches.length,
|
|
319
|
+
matches: matches.slice(0, limit),
|
|
320
|
+
extractedClassesSample: [...classSet].slice(0, 80),
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function handleGetComponentApi(ctx, args = {}) {
|
|
325
|
+
const { tagName = '', contains = '' } = args;
|
|
326
|
+
const limit = Math.min(Math.max(Number(args.limit || 10), 1), 50);
|
|
327
|
+
const customElements = await getCustomElements(ctx);
|
|
328
|
+
const tagNeedle = normalize(tagName);
|
|
329
|
+
const textNeedle = normalize(contains);
|
|
330
|
+
|
|
331
|
+
const declarations = [];
|
|
332
|
+
for (const moduleEntry of customElements.modules || []) {
|
|
333
|
+
for (const declaration of moduleEntry.declarations || []) {
|
|
334
|
+
if (!declaration.customElement || !declaration.tagName) continue;
|
|
335
|
+
const shaped = shapeComponentDeclaration(declaration);
|
|
336
|
+
const hay = normalize(JSON.stringify(shaped));
|
|
337
|
+
const tagOk = !tagNeedle || normalize(shaped.tagName) === tagNeedle || normalize(shaped.tagName).includes(tagNeedle);
|
|
338
|
+
const textOk = !textNeedle || hay.includes(textNeedle);
|
|
339
|
+
if (tagOk && textOk) declarations.push(shaped);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
ssoTRoot: normalizePath(ctx.ssoTRoot),
|
|
345
|
+
source: normalizePath(path.relative(ctx.ssoTRoot, ctx.files.customElements)),
|
|
346
|
+
totalMatches: declarations.length,
|
|
347
|
+
components: declarations.slice(0, limit),
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async function handleGetEnhancerMetadata(ctx, args = {}) {
|
|
352
|
+
const { selector = '', contains = '', includeDemoHtml = true } = args;
|
|
353
|
+
const limit = Math.min(Math.max(Number(args.limit || 20), 1), 100);
|
|
354
|
+
const list = await getEnhancerMeta(ctx);
|
|
355
|
+
const selectorNeedle = normalize(selector);
|
|
356
|
+
const textNeedle = normalize(contains);
|
|
357
|
+
|
|
358
|
+
const matches = list
|
|
359
|
+
.filter((item) => {
|
|
360
|
+
const sel = normalize(item.selector);
|
|
361
|
+
const hay = normalize(`${item.selector} ${item.description || ''} ${item.demoHtml || ''}`);
|
|
362
|
+
const selectorOk = !selectorNeedle || sel.includes(selectorNeedle);
|
|
363
|
+
const textOk = !textNeedle || hay.includes(textNeedle);
|
|
364
|
+
return selectorOk && textOk;
|
|
365
|
+
})
|
|
366
|
+
.slice(0, limit)
|
|
367
|
+
.map((item) => ({
|
|
368
|
+
selector: item.selector,
|
|
369
|
+
description: item.description || '',
|
|
370
|
+
demoHtml: includeDemoHtml ? item.demoHtml || '' : undefined,
|
|
371
|
+
}));
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
ssoTRoot: normalizePath(ctx.ssoTRoot),
|
|
375
|
+
source: normalizePath(path.relative(ctx.ssoTRoot, ctx.files.enhancersMeta)),
|
|
376
|
+
totalMatches: matches.length,
|
|
377
|
+
enhancers: matches,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async function handleGetConfigRelations(ctx, args = {}) {
|
|
382
|
+
const { pathPrefix = '', contains = '' } = args;
|
|
383
|
+
const limit = Math.min(Math.max(Number(args.limit || 60), 1), 200);
|
|
384
|
+
const relations = await getConfigRelations(ctx);
|
|
385
|
+
const pathNeedle = normalize(pathPrefix);
|
|
386
|
+
const textNeedle = normalize(contains);
|
|
387
|
+
|
|
388
|
+
const entries = Object.entries(relations)
|
|
389
|
+
.filter(([relationPath, payload]) => {
|
|
390
|
+
const pathOk = !pathNeedle || normalize(relationPath).startsWith(pathNeedle);
|
|
391
|
+
const textOk = !textNeedle || normalize(JSON.stringify(payload)).includes(textNeedle);
|
|
392
|
+
return pathOk && textOk;
|
|
393
|
+
})
|
|
394
|
+
.slice(0, limit)
|
|
395
|
+
.map(([relationPath, payload]) => ({ path: relationPath, relations: payload }));
|
|
396
|
+
|
|
397
|
+
return {
|
|
398
|
+
ssoTRoot: normalizePath(ctx.ssoTRoot),
|
|
399
|
+
source: normalizePath(path.relative(ctx.ssoTRoot, ctx.files.config)),
|
|
400
|
+
totalMatches: entries.length,
|
|
401
|
+
relations: entries,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async function handleValidateSnippet(ctx, args = {}) {
|
|
406
|
+
const html = String(args.html || '');
|
|
407
|
+
if (!html.trim()) {
|
|
408
|
+
throw new Error('The "html" argument is required.');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const [ontology, cssData, customElements] = await Promise.all([
|
|
412
|
+
getOntology(ctx),
|
|
413
|
+
getCssData(ctx),
|
|
414
|
+
getCustomElements(ctx),
|
|
415
|
+
]);
|
|
416
|
+
|
|
417
|
+
const knownTokens = new Set((cssData.properties || []).map((p) => p.name));
|
|
418
|
+
const knownComponents = new Set();
|
|
419
|
+
for (const moduleEntry of customElements.modules || []) {
|
|
420
|
+
for (const declaration of moduleEntry.declarations || []) {
|
|
421
|
+
if (declaration.customElement && declaration.tagName) knownComponents.add(declaration.tagName);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const knownClasses = new Set();
|
|
426
|
+
for (const group of [ontology.primitives || [], ontology.layoutPatterns || [], ontology.components || []]) {
|
|
427
|
+
for (const item of group) {
|
|
428
|
+
for (const selector of item.selectors || []) {
|
|
429
|
+
for (const className of getSelectorClasses(String(selector))) knownClasses.add(className);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const usedClasses = new Set();
|
|
435
|
+
const classRegex = /class\s*=\s*['\"]([^'\"]+)['\"]/g;
|
|
436
|
+
let classMatch = classRegex.exec(html);
|
|
437
|
+
while (classMatch) {
|
|
438
|
+
for (const className of classMatch[1].split(/\s+/).filter(Boolean)) {
|
|
439
|
+
usedClasses.add(className.trim());
|
|
440
|
+
}
|
|
441
|
+
classMatch = classRegex.exec(html);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const usedTokens = new Set(html.match(/--[a-zA-Z0-9-]+/g) || []);
|
|
445
|
+
|
|
446
|
+
const componentRegex = /<\s*(pds-[a-z0-9-]+)/gi;
|
|
447
|
+
const usedComponents = new Set();
|
|
448
|
+
let componentMatch = componentRegex.exec(html);
|
|
449
|
+
while (componentMatch) {
|
|
450
|
+
usedComponents.add(componentMatch[1].toLowerCase());
|
|
451
|
+
componentMatch = componentRegex.exec(html);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const unknownClasses = [...usedClasses].filter((className) => !knownClasses.has(className));
|
|
455
|
+
const unknownTokens = [...usedTokens].filter((token) => !knownTokens.has(token));
|
|
456
|
+
const unknownComponents = [...usedComponents].filter((tag) => !knownComponents.has(tag));
|
|
457
|
+
|
|
458
|
+
return {
|
|
459
|
+
ssoTRoot: normalizePath(ctx.ssoTRoot),
|
|
460
|
+
valid: unknownClasses.length === 0 && unknownTokens.length === 0 && unknownComponents.length === 0,
|
|
461
|
+
unknown: {
|
|
462
|
+
classes: unknownClasses.map((className) => ({
|
|
463
|
+
name: className,
|
|
464
|
+
suggestions: buildSuggestions([...knownClasses], className),
|
|
465
|
+
})),
|
|
466
|
+
tokens: unknownTokens.map((token) => ({
|
|
467
|
+
name: token,
|
|
468
|
+
suggestions: buildSuggestions([...knownTokens], token),
|
|
469
|
+
})),
|
|
470
|
+
components: unknownComponents.map((tag) => ({
|
|
471
|
+
name: tag,
|
|
472
|
+
suggestions: buildSuggestions([...knownComponents], tag),
|
|
473
|
+
})),
|
|
474
|
+
},
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const TOOL_HANDLERS = {
|
|
479
|
+
get_tokens: handleGetTokens,
|
|
480
|
+
find_utility_class: handleFindUtilityClass,
|
|
481
|
+
get_component_api: handleGetComponentApi,
|
|
482
|
+
get_enhancer_metadata: handleGetEnhancerMetadata,
|
|
483
|
+
get_config_relations: handleGetConfigRelations,
|
|
484
|
+
validate_pds_snippet: handleValidateSnippet,
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
export function getPdsMcpTools() {
|
|
488
|
+
return getToolSchema();
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export async function runPdsMcpTool(ctx, name, args = {}) {
|
|
492
|
+
const handler = TOOL_HANDLERS[name];
|
|
493
|
+
if (!handler) {
|
|
494
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
495
|
+
}
|
|
496
|
+
return handler(ctx, isObject(args) ? args : {});
|
|
497
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "tokens-color",
|
|
4
|
+
"tool": "get_tokens",
|
|
5
|
+
"args": {
|
|
6
|
+
"prefix": "--color-primary-",
|
|
7
|
+
"limit": 5
|
|
8
|
+
},
|
|
9
|
+
"expect": {
|
|
10
|
+
"paths": [
|
|
11
|
+
"tokens",
|
|
12
|
+
"tokens.0.name"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "utility-flex",
|
|
18
|
+
"tool": "find_utility_class",
|
|
19
|
+
"args": {
|
|
20
|
+
"query": "flex",
|
|
21
|
+
"limit": 5
|
|
22
|
+
},
|
|
23
|
+
"expect": {
|
|
24
|
+
"paths": [
|
|
25
|
+
"matches",
|
|
26
|
+
"matches.0.selector"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "component-form",
|
|
32
|
+
"tool": "get_component_api",
|
|
33
|
+
"args": {
|
|
34
|
+
"tagName": "pds-form",
|
|
35
|
+
"limit": 1
|
|
36
|
+
},
|
|
37
|
+
"expect": {
|
|
38
|
+
"paths": [
|
|
39
|
+
"components",
|
|
40
|
+
"components.0.tagName"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "enhancer-dropdown",
|
|
46
|
+
"tool": "get_enhancer_metadata",
|
|
47
|
+
"args": {
|
|
48
|
+
"selector": "data-dropdown",
|
|
49
|
+
"limit": 1
|
|
50
|
+
},
|
|
51
|
+
"expect": {
|
|
52
|
+
"paths": [
|
|
53
|
+
"enhancers",
|
|
54
|
+
"enhancers.0.selector"
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "relations-colors",
|
|
60
|
+
"tool": "get_config_relations",
|
|
61
|
+
"args": {
|
|
62
|
+
"pathPrefix": "colors",
|
|
63
|
+
"limit": 3
|
|
64
|
+
},
|
|
65
|
+
"expect": {
|
|
66
|
+
"paths": [
|
|
67
|
+
"relations",
|
|
68
|
+
"relations.0.path"
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
]
|