@monoharada/wcf-mcp 0.11.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 CHANGED
@@ -84,7 +84,7 @@ claude mcp add wcf -- npx @monoharada/wcf-mcp
84
84
 
85
85
  | ツール | 説明 |
86
86
  |--------|------|
87
- | `get_design_system_overview` | 最初に呼ぶ前提情報(カテゴリ別コンポーネント数、利用可能パターン、推奨ワークフロー、IDE設定テンプレート)を返す |
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
 
@@ -247,7 +252,7 @@ claude mcp add wcf -- npx @monoharada/wcf-mcp
247
252
 
248
253
  | URI | 説明 |
249
254
  |-----|------|
250
- | `wcf://components` | コンポーネントカタログのスナップショット |
255
+ | `wcf://components` | コンポーネントカタログのスナップショット。プロトタイピング前の preload を推奨 |
251
256
  | `wcf://tokens` | トークン summary(type/category/themes/sample) |
252
257
  | `wcf://guidelines/{topic}` | topic 別ガイドライン要約(`accessibility`,`css`,`patterns`,`all`) |
253
258
  | `wcf://llms-full` | `llms-full.txt` の全文 |
@@ -115,10 +115,14 @@ export function buildServerInstructions(prefix, installRegistry, patterns) {
115
115
  'You can generate complete HTML pages using MCP tools alone — no CLI installation required.',
116
116
  '',
117
117
  '## Quickest Workflow (build a page)',
118
- '1. get_pattern_recipe({ patternId: "<id>", include: ["fullPage"] })',
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"] })',
119
123
  ' → Returns a complete <!DOCTYPE html> page in one call',
120
- '2. validate_markup({ html: "<the full page HTML>" }) → Validate the full page (catches missing importmap / boot script)',
121
- '3. Save the fullPageHtml result to a file and serve via HTTP',
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',
122
126
  '',
123
127
  `## Available Pattern IDs (${patternIds.length})`,
124
128
  patternIds.length > 0 ? patternIds.join(', ') : '(none)',
@@ -127,9 +131,11 @@ export function buildServerInstructions(prefix, installRegistry, patterns) {
127
131
  componentIds.length > 0 ? componentIds.join(', ') : '(none)',
128
132
  '',
129
133
  '## Custom Page Construction',
130
- '1. generate_usage_snippet({ component: "<componentId>" }) → Get HTML for each component',
131
- '2. generate_full_page_html({ html: "<combined fragments>" }) Wrap into a complete page',
132
- '3. validate_markup({ html: "<the full page HTML>" }) → Validate the full page (catches missing importmap / boot script)',
134
+ '1. get_design_system_overview() → review componentPatternMap',
135
+ '2. Read resource wcf://componentspreload 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)',
133
139
  '',
134
140
  '## Important Notes',
135
141
  '- No CDN exists. All files are served from a local vendor directory.',
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 aria-label or aria-describedby support text instead of placeholder.';
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
@@ -249,6 +249,71 @@ function scoreSearchFields(query, terms, fields) {
249
249
  return score;
250
250
  }
251
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
+
252
317
  function detectKnowledgeIntentSources(query, terms) {
253
318
  const raw = `${query} ${terms.join(' ')}`.toLowerCase();
254
319
  const intents = new Set();
@@ -432,10 +497,12 @@ async function walkProjectFiles(rootDir) {
432
497
  function summarizeDiagnostics(diagnostics) {
433
498
  const errorCount = diagnostics.filter((diagnostic) => diagnostic.severity === 'error').length;
434
499
  const warningCount = diagnostics.filter((diagnostic) => diagnostic.severity === 'warning').length;
500
+ const infoCount = diagnostics.filter((diagnostic) => diagnostic.severity === 'info').length;
435
501
  return {
436
502
  total: diagnostics.length,
437
503
  errorCount,
438
504
  warningCount,
505
+ infoCount,
439
506
  };
440
507
  }
441
508
 
@@ -620,6 +687,7 @@ export function registerAll(context) {
620
687
  llmsFullText,
621
688
  tokenSuggestionMap,
622
689
  componentTokenRefMap,
690
+ selectorGuideData,
623
691
  plugins,
624
692
  loadJsonData,
625
693
  loadJson,
@@ -657,6 +725,10 @@ export function registerAll(context) {
657
725
  detectNonLowercaseAttributes,
658
726
  detectCdnReferences,
659
727
  detectMissingRuntimeScaffold,
728
+ detectTableAuthoringMisuse = () => [],
729
+ detectResourceListAuthoringMisuse = () => [],
730
+ detectReplaceableNativePatterns = () => [],
731
+ detectReplaceableAnimationPatterns = () => [],
660
732
  } = await loadValidator();
661
733
 
662
734
  const p = normalizePrefix(prefix);
@@ -757,6 +829,32 @@ export function registerAll(context) {
757
829
  severity: 'warning',
758
830
  });
759
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
+
760
858
  const allRawDiagnostics = [
761
859
  ...cemDiagnostics,
762
860
  ...enumDiagnostics,
@@ -770,6 +868,10 @@ export function registerAll(context) {
770
868
  ...accessibilityDiagnostics,
771
869
  ...cdnDiagnostics,
772
870
  ...scaffoldDiagnostics,
871
+ ...tableAuthoringDiagnostics,
872
+ ...resourceListAuthoringDiagnostics,
873
+ ...replaceableNativeDiagnostics,
874
+ ...replaceableAnimationDiagnostics,
773
875
  ];
774
876
 
775
877
  for (const plugin of plugins) {
@@ -1023,6 +1125,7 @@ export function registerAll(context) {
1023
1125
  const cat = getCategory(tagName);
1024
1126
  categoryCount[cat] = (categoryCount[cat] ?? 0) + 1;
1025
1127
  }
1128
+ const componentPatternMap = buildOverviewPatternMap(selectorGuideData);
1026
1129
 
1027
1130
  const patternList = Object.values(patterns).map((p) => ({
1028
1131
  id: p?.id,
@@ -1037,6 +1140,7 @@ export function registerAll(context) {
1037
1140
  componentsByCategory: categoryCount,
1038
1141
  totalPatterns: patternList.length,
1039
1142
  patterns: patternList,
1143
+ componentPatternMap,
1040
1144
  setupInfo: {
1041
1145
  npmPackage: 'web-components-factory',
1042
1146
  installCommand: 'npm install web-components-factory',
@@ -1118,10 +1222,10 @@ export function registerAll(context) {
1118
1222
  { name: 'get_component_selector_guide', purpose: 'Component selection guide by category and use case' },
1119
1223
  ],
1120
1224
  recommendedWorkflow: [
1121
- '1. get_design_system_overview → understand components, patterns, tokens, and IDE setup templates',
1122
- '2. figma_to_wcf (optional)bootstrap the Figma-to-WCF tool sequence',
1123
- '3. search_design_system_knowledgedo a broad first-pass search across components, patterns, tokens, guidelines, and skills',
1124
- '4. wcf://components and wcf://tokens resources preload catalog/token context',
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',
1125
1229
  '5. search_guidelines → find relevant guidelines',
1126
1230
  '6. get_design_tokens → get correct token values',
1127
1231
  '7. get_design_token_detail → inspect one token with references/referencedBy and usage examples',
@@ -1134,6 +1238,18 @@ export function registerAll(context) {
1134
1238
  '14. generate_full_page_html → wrap fragment into a complete preview-ready page',
1135
1239
  '15. get_install_recipe → get import/install instructions',
1136
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
+ ],
1137
1253
  experimental: {
1138
1254
  plugins: {
1139
1255
  enabled: plugins.length > 0,
@@ -1261,6 +1377,12 @@ export function registerAll(context) {
1261
1377
  if (related.length > 0) api.relatedComponents = related;
1262
1378
  const a11y = extractAccessibilityChecklist(d, { prefix });
1263
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;
1264
1386
  results.push(api);
1265
1387
  }
1266
1388
  return buildJsonToolResponse(results);
@@ -1301,6 +1423,10 @@ export function registerAll(context) {
1301
1423
  if (accessibilityChecklist) {
1302
1424
  api.accessibilityChecklist = accessibilityChecklist;
1303
1425
  }
1426
+ const authoringGuidance = decl?.custom?.authoringGuidance;
1427
+ if (authoringGuidance) {
1428
+ api.authoringGuidance = authoringGuidance;
1429
+ }
1304
1430
  const interactionExamples = canonicalTag ? INTERACTION_EXAMPLES_MAP[canonicalTag] : undefined;
1305
1431
  if (interactionExamples) {
1306
1432
  api.interactionExamples = interactionExamples;
@@ -1686,13 +1812,13 @@ export function registerAll(context) {
1686
1812
  },
1687
1813
  },
1688
1814
  async ({ category, useCase }) => {
1689
- if (!context.selectorGuideData || !Array.isArray(context.selectorGuideData.categories)) {
1815
+ if (!selectorGuideData || !Array.isArray(selectorGuideData.categories)) {
1690
1816
  return buildJsonToolErrorResponse({
1691
1817
  error: 'Component selector guide not available.',
1692
1818
  });
1693
1819
  }
1694
1820
 
1695
- let categories = context.selectorGuideData.categories;
1821
+ let categories = selectorGuideData.categories;
1696
1822
 
1697
1823
  if (typeof category === 'string' && category.trim()) {
1698
1824
  const cat = category.trim().toLowerCase();
@@ -1700,15 +1826,35 @@ export function registerAll(context) {
1700
1826
  }
1701
1827
 
1702
1828
  if (typeof useCase === 'string' && useCase.trim()) {
1703
- const kw = useCase.trim().toLowerCase();
1704
- categories = categories.map((c) => ({
1705
- ...c,
1706
- components: c.components.filter((comp) =>
1707
- comp.useCase.toLowerCase().includes(kw) ||
1708
- comp.id.toLowerCase().includes(kw) ||
1709
- comp.tagName.toLowerCase().includes(kw)
1710
- ),
1711
- })).filter((c) => c.components.length > 0);
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);
1712
1858
  }
1713
1859
 
1714
1860
  return buildJsonToolResponse({
@@ -2149,19 +2295,25 @@ export function registerAll(context) {
2149
2295
  const terms = expandQueryWithSynonyms(q).filter(Boolean);
2150
2296
  const limit = Number.isInteger(maxResults) ? maxResults : 10;
2151
2297
  const results = [];
2298
+ const selectorGuideLookup = buildSelectorGuideComponentLookup(selectorGuideData);
2152
2299
 
2153
2300
  if (requestedSources.has('components')) {
2154
2301
  const page = buildComponentSummaries(indexes, {
2155
- query: q,
2156
2302
  limit: 200,
2157
2303
  prefix: p,
2158
2304
  });
2159
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()] ?? ''}`);
2160
2310
  const score = scoreSearchFields(q, terms, [
2161
2311
  { text: item.tagName, weight: 5 },
2162
2312
  { text: item.className, weight: 4 },
2163
2313
  { text: item.description, weight: 2 },
2164
2314
  { text: item.category, weight: 1 },
2315
+ { text: guideEntry?.useCase, weight: 4 },
2316
+ { text: Array.isArray(guideEntry?.keywords) ? guideEntry.keywords.join(' ') : '', weight: 5 },
2165
2317
  ]);
2166
2318
  if (score <= 0) continue;
2167
2319
  results.push({
@@ -2172,6 +2324,8 @@ export function registerAll(context) {
2172
2324
  metadata: {
2173
2325
  className: item.className,
2174
2326
  category: item.category,
2327
+ useCase: guideEntry?.useCase,
2328
+ keywords: guideEntry?.keywords ?? [],
2175
2329
  },
2176
2330
  score: score + getKnowledgeSourceBoost('components', q, terms),
2177
2331
  });
package/core.mjs CHANGED
@@ -164,6 +164,10 @@ export async function createMcpServer(loadJsonData, loadValidator, options = {})
164
164
  detectNonLowercaseAttributes = () => [],
165
165
  detectCdnReferences = () => [],
166
166
  detectMissingRuntimeScaffold = () => [],
167
+ detectTableAuthoringMisuse = () => [],
168
+ detectResourceListAuthoringMisuse = () => [],
169
+ detectReplaceableNativePatterns = () => [],
170
+ detectReplaceableAnimationPatterns = () => [],
167
171
  } = await loadValidator();
168
172
  const canonicalCemIndex = collectCemCustomElements(manifest);
169
173
  const canonicalEnumMap = buildEnumAttributeMap(manifest);
@@ -251,6 +255,10 @@ export async function createMcpServer(loadJsonData, loadValidator, options = {})
251
255
  detectNonLowercaseAttributes,
252
256
  detectCdnReferences,
253
257
  detectMissingRuntimeScaffold,
258
+ detectTableAuthoringMisuse,
259
+ detectResourceListAuthoringMisuse,
260
+ detectReplaceableNativePatterns,
261
+ detectReplaceableAnimationPatterns,
254
262
  }),
255
263
  selectorGuideData,
256
264
  maxToolResultBytes: MAX_TOOL_RESULT_BYTES,