@monoharada/wcf-mcp 0.10.0 → 0.12.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/README.md +10 -4
- package/core/cem.mjs +16 -5
- package/core/constants.mjs +54 -0
- package/core/plugins.mjs +1 -0
- package/core/prefix.mjs +13 -1
- package/core/register.mjs +275 -23
- package/core.mjs +20 -5
- package/data/component-selector-guide.json +198 -56
- package/data/custom-elements.json +174 -6
- package/data/design-tokens.json +1 -1
- package/data/guidelines-index.json +1 -1
- package/data/llms-full.txt +85 -19
- package/data/pattern-registry.json +18 -0
- package/data/skills-registry.json +41 -0
- package/package.json +1 -1
- package/validator.mjs +314 -0
package/README.md
CHANGED
|
@@ -78,13 +78,13 @@ claude mcp add wcf -- npx @monoharada/wcf-mcp
|
|
|
78
78
|
}
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
## 提供機能(19 tools +
|
|
81
|
+
## 提供機能(19 tools + 2 prompts + 5 resources)
|
|
82
82
|
|
|
83
83
|
### ガードレール
|
|
84
84
|
|
|
85
85
|
| ツール | 説明 |
|
|
86
86
|
|--------|------|
|
|
87
|
-
| `get_design_system_overview` |
|
|
87
|
+
| `get_design_system_overview` | 最初に呼ぶ前提情報(カテゴリ別コンポーネント数、利用可能パターン、`componentPatternMap`、推奨 preloading / workflow、IDE設定テンプレート)を返す |
|
|
88
88
|
|
|
89
89
|
### コンポーネント検索・API
|
|
90
90
|
|
|
@@ -100,7 +100,7 @@ claude mcp add wcf -- npx @monoharada/wcf-mcp
|
|
|
100
100
|
|
|
101
101
|
| ツール | 説明 |
|
|
102
102
|
|--------|------|
|
|
103
|
-
| `validate_markup` | HTML スニペットを検証し、セマンティック検証(下表)で `suggestion`
|
|
103
|
+
| `validate_markup` | HTML スニペットを検証し、セマンティック検証(下表)で `suggestion` 付き診断を返す。severity は `error` / `warning` / `info` |
|
|
104
104
|
| `validate_files` | 複数のマークアップファイルをまとめて検証し、ファイル別診断と集計を返す |
|
|
105
105
|
| `validate_project` | ディレクトリを走査し、include/exclude glob に一致する複数ファイルをまとめて検証する |
|
|
106
106
|
|
|
@@ -126,6 +126,11 @@ claude mcp add wcf -- npx @monoharada/wcf-mcp
|
|
|
126
126
|
| `emptyAriaLabel` | error | 空の `aria-label` 属性(アクセシビリティ違反) | `<dads-button aria-label="">` |
|
|
127
127
|
| `duplicateId` | error | 同一ドキュメント内で `id` が重複している | `<div id="hero">...</div><section id="hero">...</section>` |
|
|
128
128
|
| `forbiddenAttribute` | warning | 禁止属性 | `placeholder` |
|
|
129
|
+
| `sortOnTh` / `sortWrongTarget` / `sortTypeOnWrongElement` | warning | `dads-table` のソート構造誤用 | `th[data-sort]`, `button[data-sort-type]` |
|
|
130
|
+
| `selectionControlWrongElement` | warning | `data-select-row` / `data-select-all` が checkbox 以外 | `<button data-select-row>` |
|
|
131
|
+
| `resourceListWholeLinkMissingInteraction` | warning | `dads-resource-list[href]` に `data-interaction="whole"` がない | `<dads-resource-list href="...">` |
|
|
132
|
+
| `nativePatternReplaceable` | warning / info | 既存 DADS コンポーネントに置換可能な独自パターン | `role="tablist"`, `role="dialog"`, `<dl>` |
|
|
133
|
+
| `customAnimationReplaceable` | warning | スピナー相当の独自 CSS アニメーション | `@keyframes spin` + `animation` |
|
|
129
134
|
|
|
130
135
|
### UI パターン
|
|
131
136
|
|
|
@@ -240,13 +245,14 @@ claude mcp add wcf -- npx @monoharada/wcf-mcp
|
|
|
240
245
|
|
|
241
246
|
| 名前 | 説明 |
|
|
242
247
|
|------|------|
|
|
248
|
+
| `build_page` | パターンID またはコンポーネントリストから no-build HTML ページを構築するガイド付きプロンプト |
|
|
243
249
|
| `figma_to_wcf` | Figma URL を入力に、`overview → tokens → component api → snippet → validate` の実行順を返す |
|
|
244
250
|
|
|
245
251
|
### Resources (`wcf://`)
|
|
246
252
|
|
|
247
253
|
| URI | 説明 |
|
|
248
254
|
|-----|------|
|
|
249
|
-
| `wcf://components` |
|
|
255
|
+
| `wcf://components` | コンポーネントカタログのスナップショット。プロトタイピング前の preload を推奨 |
|
|
250
256
|
| `wcf://tokens` | トークン summary(type/category/themes/sample) |
|
|
251
257
|
| `wcf://guidelines/{topic}` | topic 別ガイドライン要約(`accessibility`,`css`,`patterns`,`all`) |
|
|
252
258
|
| `wcf://llms-full` | `llms-full.txt` の全文 |
|
package/core/cem.mjs
CHANGED
|
@@ -899,7 +899,7 @@ export function resolveDeclByComponent(indexes, component, prefix) {
|
|
|
899
899
|
return undefined;
|
|
900
900
|
}
|
|
901
901
|
|
|
902
|
-
export function buildComponentNotFoundError(component, indexes, prefix) {
|
|
902
|
+
export function buildComponentNotFoundError(component, indexes, prefix, installRegistry) {
|
|
903
903
|
const comp = typeof component === 'string' ? component.trim() : '';
|
|
904
904
|
const p = normalizePrefix(prefix);
|
|
905
905
|
const suggestions = [];
|
|
@@ -916,8 +916,19 @@ export function buildComponentNotFoundError(component, indexes, prefix) {
|
|
|
916
916
|
suggestions.push(suggested);
|
|
917
917
|
}
|
|
918
918
|
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
919
|
+
const parts = [];
|
|
920
|
+
if (suggestions.length > 0) {
|
|
921
|
+
parts.push(`Component not found: ${comp}. Did you mean: ${suggestions.join(', ')}?`);
|
|
922
|
+
} else {
|
|
923
|
+
parts.push(`Component not found: ${comp}`);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
const validIds = installRegistry?.components && typeof installRegistry.components === 'object'
|
|
927
|
+
? Object.keys(installRegistry.components).sort()
|
|
928
|
+
: [];
|
|
929
|
+
if (validIds.length > 0) {
|
|
930
|
+
parts.push(`\nAvailable component IDs (${validIds.length}): ${validIds.join(', ')}`);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
return { content: [{ type: 'text', text: parts.join('') }], isError: true };
|
|
923
934
|
}
|
package/core/constants.mjs
CHANGED
|
@@ -81,6 +81,7 @@ export const CATEGORY_MAP = {
|
|
|
81
81
|
};
|
|
82
82
|
|
|
83
83
|
export const FIGMA_TO_WCF_PROMPT = 'figma_to_wcf';
|
|
84
|
+
export const BUILD_PAGE_PROMPT = 'build_page';
|
|
84
85
|
export const WCF_RESOURCE_URIS = Object.freeze({
|
|
85
86
|
components: 'wcf://components',
|
|
86
87
|
tokens: 'wcf://tokens',
|
|
@@ -94,6 +95,59 @@ const NPX_TEMPLATE = Object.freeze({
|
|
|
94
95
|
args: ['@monoharada/wcf-mcp'],
|
|
95
96
|
});
|
|
96
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Build the server-level instructions string sent to MCP clients on initialization.
|
|
100
|
+
* This enables AI agents to immediately understand what the server offers and how to
|
|
101
|
+
* build pages without any prior knowledge.
|
|
102
|
+
*/
|
|
103
|
+
export function buildServerInstructions(prefix, installRegistry, patterns) {
|
|
104
|
+
const components = installRegistry?.components && typeof installRegistry.components === 'object'
|
|
105
|
+
? installRegistry.components : {};
|
|
106
|
+
const componentIds = Object.keys(components).sort();
|
|
107
|
+
const patternIds = patterns && typeof patterns === 'object'
|
|
108
|
+
? Object.keys(patterns).sort() : [];
|
|
109
|
+
|
|
110
|
+
return [
|
|
111
|
+
'# wcf-mcp: DADS Web Components Design System MCP Server',
|
|
112
|
+
'',
|
|
113
|
+
'## Capabilities',
|
|
114
|
+
'This server provides a no-build / no-CDN Web Components design system.',
|
|
115
|
+
'You can generate complete HTML pages using MCP tools alone — no CLI installation required.',
|
|
116
|
+
'',
|
|
117
|
+
'## Quickest Workflow (build a page)',
|
|
118
|
+
'1. get_design_system_overview()',
|
|
119
|
+
' → Inspect componentPatternMap before writing custom HTML/CSS',
|
|
120
|
+
'2. Read resource wcf://components',
|
|
121
|
+
' → Preload the component catalog to avoid re-implementing existing components',
|
|
122
|
+
'3. get_pattern_recipe({ patternId: "<id>", include: ["fullPage"] })',
|
|
123
|
+
' → Returns a complete <!DOCTYPE html> page in one call',
|
|
124
|
+
'4. validate_markup({ html: "<the full page HTML>" }) → Validate the full page (catches missing importmap / boot script)',
|
|
125
|
+
'5. Save the fullPageHtml result to a file and serve via HTTP',
|
|
126
|
+
'',
|
|
127
|
+
`## Available Pattern IDs (${patternIds.length})`,
|
|
128
|
+
patternIds.length > 0 ? patternIds.join(', ') : '(none)',
|
|
129
|
+
'',
|
|
130
|
+
`## Available Component IDs (${componentIds.length})`,
|
|
131
|
+
componentIds.length > 0 ? componentIds.join(', ') : '(none)',
|
|
132
|
+
'',
|
|
133
|
+
'## Custom Page Construction',
|
|
134
|
+
'1. get_design_system_overview() → review componentPatternMap',
|
|
135
|
+
'2. Read resource wcf://components → preload component catalog context',
|
|
136
|
+
'3. generate_usage_snippet({ component: "<componentId>" }) → Get HTML for each component',
|
|
137
|
+
'4. generate_full_page_html({ html: "<combined fragments>" }) → Wrap into a complete page',
|
|
138
|
+
'5. validate_markup({ html: "<the full page HTML>" }) → Validate the full page (catches missing importmap / boot script)',
|
|
139
|
+
'',
|
|
140
|
+
'## Important Notes',
|
|
141
|
+
'- No CDN exists. All files are served from a local vendor directory.',
|
|
142
|
+
`- CLI setup: npm install web-components-factory → npx wcf init --prefix ${prefix}`,
|
|
143
|
+
'- file:// protocol does not work. An HTTP server is required.',
|
|
144
|
+
'',
|
|
145
|
+
'## Prompts',
|
|
146
|
+
'- build_page: Guided prompt for building a no-build HTML page',
|
|
147
|
+
'- figma_to_wcf: Convert Figma designs to WCF implementation',
|
|
148
|
+
].join('\n');
|
|
149
|
+
}
|
|
150
|
+
|
|
97
151
|
export const IDE_SETUP_TEMPLATES = Object.freeze([
|
|
98
152
|
{
|
|
99
153
|
ide: 'Claude Desktop',
|
package/core/plugins.mjs
CHANGED
package/core/prefix.mjs
CHANGED
|
@@ -111,7 +111,7 @@ export function buildDiagnosticSuggestion({ diagnostic, cemIndex, prefix }) {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
if (code === 'forbiddenAttribute' && String(diagnostic?.attrName ?? '').toLowerCase() === 'placeholder') {
|
|
114
|
-
return 'Use
|
|
114
|
+
return 'Use support-text (attribute or slot), a visible label, and aria-describedby/aria-label where needed instead of placeholder. See: https://design.digital.go.jp/dads/components/input-text/accessibility/';
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
if (code === 'ariaLiveNotRecommended') {
|
|
@@ -130,6 +130,18 @@ export function buildDiagnosticSuggestion({ diagnostic, cemIndex, prefix }) {
|
|
|
130
130
|
return diagnostic?.hint ?? 'Provide a meaningful aria-label value or use a visible <label> element.';
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
if (
|
|
134
|
+
code === 'sortOnTh' ||
|
|
135
|
+
code === 'sortWrongTarget' ||
|
|
136
|
+
code === 'sortTypeOnWrongElement' ||
|
|
137
|
+
code === 'selectionControlWrongElement' ||
|
|
138
|
+
code === 'resourceListWholeLinkMissingInteraction' ||
|
|
139
|
+
code === 'nativePatternReplaceable' ||
|
|
140
|
+
code === 'customAnimationReplaceable'
|
|
141
|
+
) {
|
|
142
|
+
return diagnostic?.hint ?? undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
133
145
|
return undefined;
|
|
134
146
|
}
|
|
135
147
|
|
package/core/register.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import fs from 'node:fs/promises';
|
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
8
|
import { z } from 'zod';
|
|
9
|
-
import { CANONICAL_PREFIX, PACKAGE_VERSION, PLUGIN_TOOL_NOTICE, FIGMA_TO_WCF_PROMPT, WCF_RESOURCE_URIS, IDE_SETUP_TEMPLATES } from './constants.mjs';
|
|
9
|
+
import { CANONICAL_PREFIX, PACKAGE_VERSION, PLUGIN_TOOL_NOTICE, FIGMA_TO_WCF_PROMPT, BUILD_PAGE_PROMPT, WCF_RESOURCE_URIS, IDE_SETUP_TEMPLATES } from './constants.mjs';
|
|
10
10
|
import { normalizePrefix, withPrefix, toCanonicalTagName, getCategory, buildDiagnosticSuggestion, applyPrefixToHtml, applyPrefixToTagMap, mergeWithPrefixed } from './prefix.mjs';
|
|
11
11
|
import { buildJsonToolResponse, buildJsonToolErrorResponse, expandQueryWithSynonyms, finalizeToolResult } from './response.mjs';
|
|
12
12
|
import { normalizePlugins, buildPluginDataSourceMap, toPassthroughSchema } from './plugins.mjs';
|
|
@@ -130,6 +130,77 @@ function buildFigmaToWcfPromptText({ figmaUrl, userIntent }) {
|
|
|
130
130
|
].join('\n');
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
function buildBuildPagePromptText({ patternId, components: componentsCsv, userIntent, detectedPrefix, installRegistry, patterns }) {
|
|
134
|
+
const p = normalizePrefix(detectedPrefix);
|
|
135
|
+
const intent = String(userIntent ?? '').trim();
|
|
136
|
+
const registryComponents = installRegistry?.components && typeof installRegistry.components === 'object'
|
|
137
|
+
? installRegistry.components : {};
|
|
138
|
+
const componentIds = Object.keys(registryComponents).sort();
|
|
139
|
+
const patternIds = patterns && typeof patterns === 'object'
|
|
140
|
+
? Object.keys(patterns).sort() : [];
|
|
141
|
+
|
|
142
|
+
const lines = [
|
|
143
|
+
intent ? `Page goal: ${intent}` : 'Page goal: (not specified)',
|
|
144
|
+
'',
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
// patternId takes priority over components when both are specified
|
|
148
|
+
if (patternId) {
|
|
149
|
+
lines.push(
|
|
150
|
+
'## Using a Pattern',
|
|
151
|
+
`1. get_pattern_recipe({ patternId: "${patternId}", include: ["fullPage"] })`,
|
|
152
|
+
'2. validate_markup({ html: "<the full page HTML>" }) — pass the entire page to catch missing importmap / boot script',
|
|
153
|
+
'3. Save the fullPageHtml to a .html file',
|
|
154
|
+
'',
|
|
155
|
+
);
|
|
156
|
+
if (componentsCsv) {
|
|
157
|
+
lines.push(
|
|
158
|
+
'> Note: patternId was specified, so the components argument is ignored. Remove patternId to use individual components instead.',
|
|
159
|
+
'',
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
} else if (componentsCsv) {
|
|
163
|
+
const ids = componentsCsv.split(',').map((s) => s.trim()).filter(Boolean);
|
|
164
|
+
lines.push(
|
|
165
|
+
'## Using Specific Components',
|
|
166
|
+
...ids.map((id) => `- generate_usage_snippet({ component: "${id}" })`),
|
|
167
|
+
'- Combine the HTML fragments',
|
|
168
|
+
'- generate_full_page_html({ html: "<combined fragments>" }) → returns fullHtml',
|
|
169
|
+
'- validate_markup({ html: "<the full page HTML>" }) — pass the entire page to catch missing importmap / boot script',
|
|
170
|
+
'',
|
|
171
|
+
);
|
|
172
|
+
} else {
|
|
173
|
+
lines.push(
|
|
174
|
+
'## Workflow Options',
|
|
175
|
+
'',
|
|
176
|
+
'### Option A: Use a pattern (recommended)',
|
|
177
|
+
'1. get_pattern_recipe({ patternId: "<id>", include: ["fullPage"] })',
|
|
178
|
+
'2. validate_markup({ html: "<the full page HTML>" }) — pass the entire page to catch missing importmap / boot script',
|
|
179
|
+
'3. Save the fullPageHtml to a .html file',
|
|
180
|
+
'',
|
|
181
|
+
'### Option B: Build from individual components',
|
|
182
|
+
'1. generate_usage_snippet({ component: "<componentId>" }) for each component',
|
|
183
|
+
'2. Combine the HTML fragments',
|
|
184
|
+
'3. generate_full_page_html({ html: "<combined fragments>" }) → returns fullHtml',
|
|
185
|
+
'4. validate_markup({ html: "<the full page HTML>" }) — pass the entire page to catch missing importmap / boot script',
|
|
186
|
+
'',
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
lines.push(
|
|
191
|
+
`## Available Pattern IDs (${patternIds.length})`,
|
|
192
|
+
patternIds.length > 0 ? patternIds.join(', ') : '(none)',
|
|
193
|
+
'',
|
|
194
|
+
`## Available Component IDs (${componentIds.length})`,
|
|
195
|
+
componentIds.length > 0 ? componentIds.join(', ') : '(none)',
|
|
196
|
+
'',
|
|
197
|
+
'## CLI Vendor Setup (alternative)',
|
|
198
|
+
`npx web-components-factory init --prefix ${p} --dir .` + (patternId ? ` --pattern ${patternId}` : ''),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
return lines.join('\n');
|
|
202
|
+
}
|
|
203
|
+
|
|
133
204
|
function escapeHtmlTitle(s) {
|
|
134
205
|
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
135
206
|
}
|
|
@@ -178,6 +249,71 @@ function scoreSearchFields(query, terms, fields) {
|
|
|
178
249
|
return score;
|
|
179
250
|
}
|
|
180
251
|
|
|
252
|
+
function normalizeSearchText(value) {
|
|
253
|
+
return String(value ?? '').trim().toLowerCase();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function normalizeSearchTokens(value) {
|
|
257
|
+
return normalizeSearchText(value)
|
|
258
|
+
.split(/[^a-z0-9\u3040-\u30ff\u3400-\u9fff-]+/u)
|
|
259
|
+
.filter(Boolean);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function includesWholeToken(value, query) {
|
|
263
|
+
return normalizeSearchTokens(value).includes(normalizeSearchText(query));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function buildSelectorGuideComponentLookup(selectorGuideData) {
|
|
267
|
+
const lookup = new Map();
|
|
268
|
+
const categories = Array.isArray(selectorGuideData?.categories) ? selectorGuideData.categories : [];
|
|
269
|
+
for (const category of categories) {
|
|
270
|
+
const categoryKey = String(category?.key ?? '');
|
|
271
|
+
const components = Array.isArray(category?.components) ? category.components : [];
|
|
272
|
+
for (const component of components) {
|
|
273
|
+
const id = String(component?.id ?? '').trim();
|
|
274
|
+
const tagName = String(component?.tagName ?? '').trim().toLowerCase();
|
|
275
|
+
const useCase = String(component?.useCase ?? '');
|
|
276
|
+
const keywords = Array.isArray(component?.keywords) ? component.keywords.map((keyword) => String(keyword)) : [];
|
|
277
|
+
const entry = { id, tagName, useCase, keywords, categoryKey };
|
|
278
|
+
if (id) lookup.set(`id:${id}`, entry);
|
|
279
|
+
if (tagName) lookup.set(`tag:${tagName}`, entry);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return lookup;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function scoreSelectorGuideComponent(component, query) {
|
|
286
|
+
const q = normalizeSearchText(query);
|
|
287
|
+
if (!q) return 0;
|
|
288
|
+
|
|
289
|
+
const id = String(component?.id ?? '');
|
|
290
|
+
const tagName = String(component?.tagName ?? '').toLowerCase();
|
|
291
|
+
const useCase = String(component?.useCase ?? '');
|
|
292
|
+
const keywords = Array.isArray(component?.keywords) ? component.keywords : [];
|
|
293
|
+
|
|
294
|
+
if (id.toLowerCase() === q) return 120;
|
|
295
|
+
if (tagName === q || tagName === `dads-${q}`) return 115;
|
|
296
|
+
if (keywords.some((keyword) => normalizeSearchText(keyword) === q)) return 110;
|
|
297
|
+
if (includesWholeToken(useCase, q)) return 90;
|
|
298
|
+
if (keywords.some((keyword) => includesWholeToken(keyword, q))) return 85;
|
|
299
|
+
if (tagName.startsWith(q) || id.toLowerCase().startsWith(q)) return 70;
|
|
300
|
+
if (useCase.toLowerCase().includes(q)) return 40;
|
|
301
|
+
if (keywords.some((keyword) => normalizeSearchText(keyword).includes(q))) return 35;
|
|
302
|
+
if (tagName.includes(q) || id.toLowerCase().includes(q)) return 20;
|
|
303
|
+
return 0;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function buildOverviewPatternMap(selectorGuideData) {
|
|
307
|
+
const entries = Array.isArray(selectorGuideData?.componentPatternMap) ? selectorGuideData.componentPatternMap : [];
|
|
308
|
+
return entries.map((entry) => ({
|
|
309
|
+
pattern: String(entry?.pattern ?? ''),
|
|
310
|
+
componentIds: Array.isArray(entry?.componentIds) ? entry.componentIds.map((item) => String(item)) : [],
|
|
311
|
+
usage: typeof entry?.usage === 'string' ? entry.usage : undefined,
|
|
312
|
+
note: String(entry?.note ?? ''),
|
|
313
|
+
keywords: Array.isArray(entry?.keywords) ? entry.keywords.map((item) => String(item)) : [],
|
|
314
|
+
})).filter((entry) => entry.pattern && entry.componentIds.length > 0);
|
|
315
|
+
}
|
|
316
|
+
|
|
181
317
|
function detectKnowledgeIntentSources(query, terms) {
|
|
182
318
|
const raw = `${query} ${terms.join(' ')}`.toLowerCase();
|
|
183
319
|
const intents = new Set();
|
|
@@ -361,10 +497,12 @@ async function walkProjectFiles(rootDir) {
|
|
|
361
497
|
function summarizeDiagnostics(diagnostics) {
|
|
362
498
|
const errorCount = diagnostics.filter((diagnostic) => diagnostic.severity === 'error').length;
|
|
363
499
|
const warningCount = diagnostics.filter((diagnostic) => diagnostic.severity === 'warning').length;
|
|
500
|
+
const infoCount = diagnostics.filter((diagnostic) => diagnostic.severity === 'info').length;
|
|
364
501
|
return {
|
|
365
502
|
total: diagnostics.length,
|
|
366
503
|
errorCount,
|
|
367
504
|
warningCount,
|
|
505
|
+
infoCount,
|
|
368
506
|
};
|
|
369
507
|
}
|
|
370
508
|
|
|
@@ -549,6 +687,7 @@ export function registerAll(context) {
|
|
|
549
687
|
llmsFullText,
|
|
550
688
|
tokenSuggestionMap,
|
|
551
689
|
componentTokenRefMap,
|
|
690
|
+
selectorGuideData,
|
|
552
691
|
plugins,
|
|
553
692
|
loadJsonData,
|
|
554
693
|
loadJson,
|
|
@@ -586,6 +725,10 @@ export function registerAll(context) {
|
|
|
586
725
|
detectNonLowercaseAttributes,
|
|
587
726
|
detectCdnReferences,
|
|
588
727
|
detectMissingRuntimeScaffold,
|
|
728
|
+
detectTableAuthoringMisuse = () => [],
|
|
729
|
+
detectResourceListAuthoringMisuse = () => [],
|
|
730
|
+
detectReplaceableNativePatterns = () => [],
|
|
731
|
+
detectReplaceableAnimationPatterns = () => [],
|
|
589
732
|
} = await loadValidator();
|
|
590
733
|
|
|
591
734
|
const p = normalizePrefix(prefix);
|
|
@@ -686,6 +829,32 @@ export function registerAll(context) {
|
|
|
686
829
|
severity: 'warning',
|
|
687
830
|
});
|
|
688
831
|
|
|
832
|
+
const tableAuthoringDiagnostics = detectTableAuthoringMisuse({
|
|
833
|
+
filePath,
|
|
834
|
+
text,
|
|
835
|
+
prefix: p,
|
|
836
|
+
severity: 'warning',
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
const resourceListAuthoringDiagnostics = detectResourceListAuthoringMisuse({
|
|
840
|
+
filePath,
|
|
841
|
+
text,
|
|
842
|
+
prefix: p,
|
|
843
|
+
severity: 'warning',
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
const replaceableNativeDiagnostics = detectReplaceableNativePatterns({
|
|
847
|
+
filePath,
|
|
848
|
+
text,
|
|
849
|
+
prefix: p,
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
const replaceableAnimationDiagnostics = detectReplaceableAnimationPatterns({
|
|
853
|
+
filePath,
|
|
854
|
+
text,
|
|
855
|
+
prefix: p,
|
|
856
|
+
});
|
|
857
|
+
|
|
689
858
|
const allRawDiagnostics = [
|
|
690
859
|
...cemDiagnostics,
|
|
691
860
|
...enumDiagnostics,
|
|
@@ -699,6 +868,10 @@ export function registerAll(context) {
|
|
|
699
868
|
...accessibilityDiagnostics,
|
|
700
869
|
...cdnDiagnostics,
|
|
701
870
|
...scaffoldDiagnostics,
|
|
871
|
+
...tableAuthoringDiagnostics,
|
|
872
|
+
...resourceListAuthoringDiagnostics,
|
|
873
|
+
...replaceableNativeDiagnostics,
|
|
874
|
+
...replaceableAnimationDiagnostics,
|
|
702
875
|
];
|
|
703
876
|
|
|
704
877
|
for (const plugin of plugins) {
|
|
@@ -770,6 +943,32 @@ export function registerAll(context) {
|
|
|
770
943
|
}),
|
|
771
944
|
);
|
|
772
945
|
|
|
946
|
+
// -----------------------------------------------------------------------
|
|
947
|
+
// Prompt: build_page
|
|
948
|
+
// -----------------------------------------------------------------------
|
|
949
|
+
server.registerPrompt(
|
|
950
|
+
BUILD_PAGE_PROMPT,
|
|
951
|
+
{
|
|
952
|
+
title: 'Build Page',
|
|
953
|
+
description:
|
|
954
|
+
'Guided prompt for building a no-build HTML page from a pattern or component list.',
|
|
955
|
+
argsSchema: {
|
|
956
|
+
patternId: z.string().optional().describe('Pattern ID (e.g., "search-results", "card-grid"). Use list_patterns to see all.'),
|
|
957
|
+
components: z.string().optional().describe('Comma-separated component IDs if not using a pattern'),
|
|
958
|
+
userIntent: z.string().optional().describe('What the page should accomplish'),
|
|
959
|
+
},
|
|
960
|
+
},
|
|
961
|
+
async ({ patternId, components: componentsCsv, userIntent }) => ({
|
|
962
|
+
messages: [{
|
|
963
|
+
role: 'user',
|
|
964
|
+
content: {
|
|
965
|
+
type: 'text',
|
|
966
|
+
text: buildBuildPagePromptText({ patternId, components: componentsCsv, userIntent, detectedPrefix, installRegistry, patterns }),
|
|
967
|
+
},
|
|
968
|
+
}],
|
|
969
|
+
}),
|
|
970
|
+
);
|
|
971
|
+
|
|
773
972
|
// -----------------------------------------------------------------------
|
|
774
973
|
// Resource: wcf://components
|
|
775
974
|
// -----------------------------------------------------------------------
|
|
@@ -926,6 +1125,7 @@ export function registerAll(context) {
|
|
|
926
1125
|
const cat = getCategory(tagName);
|
|
927
1126
|
categoryCount[cat] = (categoryCount[cat] ?? 0) + 1;
|
|
928
1127
|
}
|
|
1128
|
+
const componentPatternMap = buildOverviewPatternMap(selectorGuideData);
|
|
929
1129
|
|
|
930
1130
|
const patternList = Object.values(patterns).map((p) => ({
|
|
931
1131
|
id: p?.id,
|
|
@@ -940,6 +1140,7 @@ export function registerAll(context) {
|
|
|
940
1140
|
componentsByCategory: categoryCount,
|
|
941
1141
|
totalPatterns: patternList.length,
|
|
942
1142
|
patterns: patternList,
|
|
1143
|
+
componentPatternMap,
|
|
943
1144
|
setupInfo: {
|
|
944
1145
|
npmPackage: 'web-components-factory',
|
|
945
1146
|
installCommand: 'npm install web-components-factory',
|
|
@@ -987,6 +1188,10 @@ export function registerAll(context) {
|
|
|
987
1188
|
name: FIGMA_TO_WCF_PROMPT,
|
|
988
1189
|
purpose: 'Figma-to-WCF conversion workflow prompt',
|
|
989
1190
|
},
|
|
1191
|
+
{
|
|
1192
|
+
name: BUILD_PAGE_PROMPT,
|
|
1193
|
+
purpose: 'Build a no-build HTML page from a pattern or component list',
|
|
1194
|
+
},
|
|
990
1195
|
],
|
|
991
1196
|
availableResources: [
|
|
992
1197
|
{ uri: WCF_RESOURCE_URIS.components, purpose: 'Component catalog snapshot' },
|
|
@@ -1017,10 +1222,10 @@ export function registerAll(context) {
|
|
|
1017
1222
|
{ name: 'get_component_selector_guide', purpose: 'Component selection guide by category and use case' },
|
|
1018
1223
|
],
|
|
1019
1224
|
recommendedWorkflow: [
|
|
1020
|
-
'1. get_design_system_overview →
|
|
1021
|
-
'2.
|
|
1022
|
-
'3.
|
|
1023
|
-
'4.
|
|
1225
|
+
'1. get_design_system_overview → start here and inspect componentPatternMap before creating custom HTML/CSS',
|
|
1226
|
+
'2. wcf://components and wcf://tokens resources → preload the component/token catalog',
|
|
1227
|
+
'3. figma_to_wcf (optional) → bootstrap the Figma-to-WCF tool sequence',
|
|
1228
|
+
'4. search_design_system_knowledge → do a broad first-pass search across components, patterns, tokens, guidelines, and skills',
|
|
1024
1229
|
'5. search_guidelines → find relevant guidelines',
|
|
1025
1230
|
'6. get_design_tokens → get correct token values',
|
|
1026
1231
|
'7. get_design_token_detail → inspect one token with references/referencedBy and usage examples',
|
|
@@ -1033,6 +1238,18 @@ export function registerAll(context) {
|
|
|
1033
1238
|
'14. generate_full_page_html → wrap fragment into a complete preview-ready page',
|
|
1034
1239
|
'15. get_install_recipe → get import/install instructions',
|
|
1035
1240
|
],
|
|
1241
|
+
recommendedPreloadResources: [
|
|
1242
|
+
{
|
|
1243
|
+
uri: WCF_RESOURCE_URIS.components,
|
|
1244
|
+
reason: 'Read this before prototyping to avoid re-implementing existing WCF components.',
|
|
1245
|
+
when: 'Before generating page markup',
|
|
1246
|
+
},
|
|
1247
|
+
{
|
|
1248
|
+
uri: WCF_RESOURCE_URIS.tokens,
|
|
1249
|
+
reason: 'Read this before styling to avoid hard-coded token values.',
|
|
1250
|
+
when: 'Before writing CSS or inline styles',
|
|
1251
|
+
},
|
|
1252
|
+
],
|
|
1036
1253
|
experimental: {
|
|
1037
1254
|
plugins: {
|
|
1038
1255
|
enabled: plugins.length > 0,
|
|
@@ -1160,6 +1377,12 @@ export function registerAll(context) {
|
|
|
1160
1377
|
if (related.length > 0) api.relatedComponents = related;
|
|
1161
1378
|
const a11y = extractAccessibilityChecklist(d, { prefix });
|
|
1162
1379
|
if (a11y) api.accessibilityChecklist = a11y;
|
|
1380
|
+
const interactionExamples = cTag ? INTERACTION_EXAMPLES_MAP[cTag] : undefined;
|
|
1381
|
+
if (interactionExamples) api.interactionExamples = interactionExamples;
|
|
1382
|
+
const layoutBehavior = cTag ? LAYOUT_BEHAVIOR_MAP[cTag] : undefined;
|
|
1383
|
+
if (layoutBehavior) api.layoutBehavior = layoutBehavior;
|
|
1384
|
+
const authoringGuidance = d?.custom?.authoringGuidance;
|
|
1385
|
+
if (authoringGuidance) api.authoringGuidance = authoringGuidance;
|
|
1163
1386
|
results.push(api);
|
|
1164
1387
|
}
|
|
1165
1388
|
return buildJsonToolResponse(results);
|
|
@@ -1179,7 +1402,7 @@ export function registerAll(context) {
|
|
|
1179
1402
|
|
|
1180
1403
|
if (!decl) {
|
|
1181
1404
|
const identifier = component || tagName || className || '';
|
|
1182
|
-
return buildComponentNotFoundError(identifier, indexes, p);
|
|
1405
|
+
return buildComponentNotFoundError(identifier, indexes, p, installRegistry);
|
|
1183
1406
|
}
|
|
1184
1407
|
|
|
1185
1408
|
const canonicalTag = typeof decl.tagName === 'string' ? decl.tagName.toLowerCase() : undefined;
|
|
@@ -1200,6 +1423,10 @@ export function registerAll(context) {
|
|
|
1200
1423
|
if (accessibilityChecklist) {
|
|
1201
1424
|
api.accessibilityChecklist = accessibilityChecklist;
|
|
1202
1425
|
}
|
|
1426
|
+
const authoringGuidance = decl?.custom?.authoringGuidance;
|
|
1427
|
+
if (authoringGuidance) {
|
|
1428
|
+
api.authoringGuidance = authoringGuidance;
|
|
1429
|
+
}
|
|
1203
1430
|
const interactionExamples = canonicalTag ? INTERACTION_EXAMPLES_MAP[canonicalTag] : undefined;
|
|
1204
1431
|
if (interactionExamples) {
|
|
1205
1432
|
api.interactionExamples = interactionExamples;
|
|
@@ -1232,7 +1459,7 @@ export function registerAll(context) {
|
|
|
1232
1459
|
const decl = resolved?.decl;
|
|
1233
1460
|
|
|
1234
1461
|
if (!decl) {
|
|
1235
|
-
return buildComponentNotFoundError(component, indexes, p);
|
|
1462
|
+
return buildComponentNotFoundError(component, indexes, p, installRegistry);
|
|
1236
1463
|
}
|
|
1237
1464
|
|
|
1238
1465
|
const canonicalTag = typeof decl.tagName === 'string' ? decl.tagName.toLowerCase() : undefined;
|
|
@@ -1265,10 +1492,7 @@ export function registerAll(context) {
|
|
|
1265
1492
|
const decl = resolved?.decl;
|
|
1266
1493
|
|
|
1267
1494
|
if (!decl) {
|
|
1268
|
-
return
|
|
1269
|
-
content: [{ type: 'text', text: `Component not found: ${component}` }],
|
|
1270
|
-
isError: true,
|
|
1271
|
-
};
|
|
1495
|
+
return buildComponentNotFoundError(component, indexes, p, installRegistry);
|
|
1272
1496
|
}
|
|
1273
1497
|
|
|
1274
1498
|
const canonicalTag = typeof decl.tagName === 'string' ? decl.tagName.toLowerCase() : undefined;
|
|
@@ -1588,13 +1812,13 @@ export function registerAll(context) {
|
|
|
1588
1812
|
},
|
|
1589
1813
|
},
|
|
1590
1814
|
async ({ category, useCase }) => {
|
|
1591
|
-
if (!
|
|
1815
|
+
if (!selectorGuideData || !Array.isArray(selectorGuideData.categories)) {
|
|
1592
1816
|
return buildJsonToolErrorResponse({
|
|
1593
1817
|
error: 'Component selector guide not available.',
|
|
1594
1818
|
});
|
|
1595
1819
|
}
|
|
1596
1820
|
|
|
1597
|
-
let categories =
|
|
1821
|
+
let categories = selectorGuideData.categories;
|
|
1598
1822
|
|
|
1599
1823
|
if (typeof category === 'string' && category.trim()) {
|
|
1600
1824
|
const cat = category.trim().toLowerCase();
|
|
@@ -1602,15 +1826,35 @@ export function registerAll(context) {
|
|
|
1602
1826
|
}
|
|
1603
1827
|
|
|
1604
1828
|
if (typeof useCase === 'string' && useCase.trim()) {
|
|
1605
|
-
const
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1829
|
+
const scored = [];
|
|
1830
|
+
for (const categoryEntry of categories) {
|
|
1831
|
+
const components = Array.isArray(categoryEntry?.components) ? categoryEntry.components : [];
|
|
1832
|
+
for (const component of components) {
|
|
1833
|
+
const score = scoreSelectorGuideComponent(component, useCase);
|
|
1834
|
+
if (score <= 0) continue;
|
|
1835
|
+
scored.push({
|
|
1836
|
+
categoryKey: categoryEntry.key,
|
|
1837
|
+
component: { ...component, _score: score },
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
const grouped = new Map();
|
|
1843
|
+
for (const hit of scored.sort((left, right) => right.component._score - left.component._score)) {
|
|
1844
|
+
const list = grouped.get(hit.categoryKey) ?? [];
|
|
1845
|
+
list.push(hit.component);
|
|
1846
|
+
grouped.set(hit.categoryKey, list);
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
categories = categories
|
|
1850
|
+
.map((categoryEntry) => ({
|
|
1851
|
+
...categoryEntry,
|
|
1852
|
+
components: (grouped.get(categoryEntry.key) ?? []).map((component) => {
|
|
1853
|
+
const { _score, ...rest } = component;
|
|
1854
|
+
return rest;
|
|
1855
|
+
}),
|
|
1856
|
+
}))
|
|
1857
|
+
.filter((categoryEntry) => categoryEntry.components.length > 0);
|
|
1614
1858
|
}
|
|
1615
1859
|
|
|
1616
1860
|
return buildJsonToolResponse({
|
|
@@ -2051,19 +2295,25 @@ export function registerAll(context) {
|
|
|
2051
2295
|
const terms = expandQueryWithSynonyms(q).filter(Boolean);
|
|
2052
2296
|
const limit = Number.isInteger(maxResults) ? maxResults : 10;
|
|
2053
2297
|
const results = [];
|
|
2298
|
+
const selectorGuideLookup = buildSelectorGuideComponentLookup(selectorGuideData);
|
|
2054
2299
|
|
|
2055
2300
|
if (requestedSources.has('components')) {
|
|
2056
2301
|
const page = buildComponentSummaries(indexes, {
|
|
2057
|
-
query: q,
|
|
2058
2302
|
limit: 200,
|
|
2059
2303
|
prefix: p,
|
|
2060
2304
|
});
|
|
2061
2305
|
for (const item of page.items) {
|
|
2306
|
+
const canonicalTagName = toCanonicalTagName(item.tagName, p) ?? item.tagName;
|
|
2307
|
+
const guideEntry =
|
|
2308
|
+
selectorGuideLookup.get(`tag:${String(canonicalTagName).toLowerCase()}`) ??
|
|
2309
|
+
selectorGuideLookup.get(`id:${installRegistry?.tags?.[String(canonicalTagName).toLowerCase()] ?? ''}`);
|
|
2062
2310
|
const score = scoreSearchFields(q, terms, [
|
|
2063
2311
|
{ text: item.tagName, weight: 5 },
|
|
2064
2312
|
{ text: item.className, weight: 4 },
|
|
2065
2313
|
{ text: item.description, weight: 2 },
|
|
2066
2314
|
{ text: item.category, weight: 1 },
|
|
2315
|
+
{ text: guideEntry?.useCase, weight: 4 },
|
|
2316
|
+
{ text: Array.isArray(guideEntry?.keywords) ? guideEntry.keywords.join(' ') : '', weight: 5 },
|
|
2067
2317
|
]);
|
|
2068
2318
|
if (score <= 0) continue;
|
|
2069
2319
|
results.push({
|
|
@@ -2074,6 +2324,8 @@ export function registerAll(context) {
|
|
|
2074
2324
|
metadata: {
|
|
2075
2325
|
className: item.className,
|
|
2076
2326
|
category: item.category,
|
|
2327
|
+
useCase: guideEntry?.useCase,
|
|
2328
|
+
keywords: guideEntry?.keywords ?? [],
|
|
2077
2329
|
},
|
|
2078
2330
|
score: score + getKnowledgeSourceBoost('components', q, terms),
|
|
2079
2331
|
});
|