@fragments-sdk/mcp 0.8.1 → 0.9.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.
@@ -470,14 +470,14 @@ function reciprocalRankFusion(resultSets, k = 60) {
470
470
  const scoreMap = /* @__PURE__ */ new Map();
471
471
  for (const { results } of resultSets) {
472
472
  for (let rank = 0; rank < results.length; rank++) {
473
- const result = results[rank];
474
- const key = `${result.kind}:${result.name}`;
473
+ const result2 = results[rank];
474
+ const key = `${result2.kind}:${result2.name}`;
475
475
  const rrfScore = 1 / (k + rank + 1);
476
476
  const existing = scoreMap.get(key);
477
477
  if (existing) {
478
478
  existing.score += rrfScore;
479
479
  } else {
480
- scoreMap.set(key, { score: rrfScore, kind: result.kind, name: result.name });
480
+ scoreMap.set(key, { score: rrfScore, kind: result2.kind, name: result2.name });
481
481
  }
482
482
  }
483
483
  }
@@ -521,8 +521,8 @@ async function hybridSearch(query, data, limit = 10, kind, apiKey) {
521
521
  const engine = new ComponentGraphEngine2(graph);
522
522
  const topComponents = [...keywordResults, ...vectorResults].filter((r) => r.kind === "component").slice(0, 5);
523
523
  const neighborSet = /* @__PURE__ */ new Set();
524
- for (const result of topComponents) {
525
- const neighbors = engine.neighbors(result.name, 1);
524
+ for (const result2 of topComponents) {
525
+ const neighbors = engine.neighbors(result2.name, 1);
526
526
  for (const n of neighbors.neighbors) {
527
527
  if (!neighborSet.has(n.component)) {
528
528
  neighborSet.add(n.component);
@@ -609,10 +609,10 @@ function buildBlockComponentFrequency(blocks) {
609
609
  return freq;
610
610
  }
611
611
  function boostByBlockFrequency(results, freq) {
612
- for (const result of results) {
613
- const count = freq.get(result.name.toLowerCase()) ?? 0;
612
+ for (const result2 of results) {
613
+ const count = freq.get(result2.name.toLowerCase()) ?? 0;
614
614
  if (count > 0) {
615
- result.score += count * BLOCK_BOOST_PER_OCCURRENCE;
615
+ result2.score += count * BLOCK_BOOST_PER_OCCURRENCE;
616
616
  }
617
617
  }
618
618
  results.sort((a, b) => b.score - a.score);
@@ -843,7 +843,7 @@ var discoverHandler = async (args, ctx) => {
843
843
  ctx.indexes.componentIndex ?? void 0
844
844
  );
845
845
  const allowedNames = new Set(components2.map((component) => component.name));
846
- const sortedNames = scored.filter((result) => allowedNames.has(result.name)).map((result) => result.name.toLowerCase());
846
+ const sortedNames = scored.filter((result2) => allowedNames.has(result2.name)).map((result2) => result2.name.toLowerCase());
847
847
  components2 = components2.filter((component) => sortedNames.includes(component.name.toLowerCase())).sort(
848
848
  (a, b) => sortedNames.indexOf(a.name.toLowerCase()) - sortedNames.indexOf(b.name.toLowerCase())
849
849
  );
@@ -898,12 +898,12 @@ var discoverHandler = async (args, ctx) => {
898
898
  "component",
899
899
  ctx.config.searchApiKey
900
900
  );
901
- const filteredSearchResults = searchResults.filter((result) => {
902
- const component = componentsByName.get(result.name.toLowerCase());
901
+ const filteredSearchResults = searchResults.filter((result2) => {
902
+ const component = componentsByName.get(result2.name.toLowerCase());
903
903
  if (!component) return false;
904
904
  if (category && !categoryMatches(component.category, category)) return false;
905
905
  if (status && component.status !== status) return false;
906
- result.score += getRankingBonus(component);
906
+ result2.score += getRankingBonus(component);
907
907
  return true;
908
908
  });
909
909
  const blockMatches = keywordScoreBlocks(
@@ -919,15 +919,15 @@ var discoverHandler = async (args, ctx) => {
919
919
  boostByBlockFrequency(filteredSearchResults, blockFreq);
920
920
  }
921
921
  const maxScore = filteredSearchResults.length > 0 ? filteredSearchResults[0].score : 0;
922
- const scored = filteredSearchResults.map((result) => {
923
- const component = componentsByName.get(result.name.toLowerCase());
922
+ const scored = filteredSearchResults.map((result2) => {
923
+ const component = componentsByName.get(result2.name.toLowerCase());
924
924
  if (!component) return null;
925
925
  return {
926
926
  component: component.name,
927
927
  category: component.category,
928
928
  description: component.description,
929
- confidence: assignConfidence(result.score, maxScore),
930
- reasons: [`Matched via hybrid search (score: ${result.score.toFixed(4)})`],
929
+ confidence: assignConfidence(result2.score, maxScore),
930
+ reasons: [`Matched via hybrid search (score: ${result2.score.toFixed(4)})`],
931
931
  usage: {
932
932
  when: getGuidanceWhen(component).slice(0, 3),
933
933
  whenNot: getGuidanceWhenNot(component).slice(0, 2)
@@ -1009,13 +1009,13 @@ var discoverHandler = async (args, ctx) => {
1009
1009
  ]);
1010
1010
  const topBlockScore = blockSearchResults.length > 0 ? blockSearchResults[0].score : 0;
1011
1011
  const relevantBlockResults = blockSearchResults.filter(
1012
- (result) => result.score >= topBlockScore * 0.3
1012
+ (result2) => result2.score >= topBlockScore * 0.3
1013
1013
  );
1014
1014
  if (relevantBlockResults.length > 0) {
1015
1015
  fullBlocks = (await Promise.all(
1016
- relevantBlockResults.slice(0, 5).map(async (result) => {
1016
+ relevantBlockResults.slice(0, 5).map(async (result2) => {
1017
1017
  const block = allBlocks.find(
1018
- (entry) => entry.name.toLowerCase() === result.name.toLowerCase()
1018
+ (entry) => entry.name.toLowerCase() === result2.name.toLowerCase()
1019
1019
  );
1020
1020
  if (!block) return null;
1021
1021
  const imports = await buildImportStatements(
@@ -1043,11 +1043,11 @@ var discoverHandler = async (args, ctx) => {
1043
1043
  tokensByName.set(token.name, cat);
1044
1044
  }
1045
1045
  }
1046
- for (const result of tokenSearchResults) {
1047
- const cat = tokensByName.get(result.name);
1046
+ for (const result2 of tokenSearchResults) {
1047
+ const cat = tokensByName.get(result2.name);
1048
1048
  if (cat) {
1049
1049
  if (!fullTokens[cat]) fullTokens[cat] = [];
1050
- fullTokens[cat].push(result.name);
1050
+ fullTokens[cat].push(result2.name);
1051
1051
  }
1052
1052
  }
1053
1053
  if (Object.keys(fullTokens).length === 0) fullTokens = void 0;
@@ -1172,14 +1172,14 @@ var discoverHandler = async (args, ctx) => {
1172
1172
  search2,
1173
1173
  filteredComponents,
1174
1174
  ctx.indexes.componentIndex ?? void 0
1175
- ).map((result) => {
1175
+ ).map((result2) => {
1176
1176
  const component = filteredComponents.find(
1177
- (entry) => entry.name.toLowerCase() === result.name.toLowerCase()
1177
+ (entry) => entry.name.toLowerCase() === result2.name.toLowerCase()
1178
1178
  );
1179
1179
  if (!component) return null;
1180
1180
  return {
1181
1181
  component,
1182
- score: result.score + getRankingBonus(component)
1182
+ score: result2.score + getRankingBonus(component)
1183
1183
  };
1184
1184
  }).filter(Boolean);
1185
1185
  filteredComponents = scored.sort((a, b) => b.score - a.score).map((entry) => entry.component);
@@ -1248,11 +1248,11 @@ function projectFields(obj, fields) {
1248
1248
  if (!fields || fields.length === 0) {
1249
1249
  return obj;
1250
1250
  }
1251
- const result = {};
1251
+ const result2 = {};
1252
1252
  for (const field of fields) {
1253
1253
  const parts = field.split(".");
1254
1254
  let source = obj;
1255
- let target = result;
1255
+ let target = result2;
1256
1256
  for (let i = 0; i < parts.length; i++) {
1257
1257
  const part = parts[i];
1258
1258
  const isLast = i === parts.length - 1;
@@ -1272,7 +1272,7 @@ function projectFields(obj, fields) {
1272
1272
  }
1273
1273
  }
1274
1274
  }
1275
- return result;
1275
+ return result2;
1276
1276
  }
1277
1277
 
1278
1278
  // src/tools/inspect.ts
@@ -1441,23 +1441,23 @@ var inspectHandler = async (args, ctx) => {
1441
1441
  if (aliasMap[parts[0]]) parts[0] = aliasMap[parts[0]];
1442
1442
  return parts.join(".");
1443
1443
  });
1444
- let result;
1444
+ let result2;
1445
1445
  if (verbosity === "compact" && !resolvedFields?.length) {
1446
- result = {
1446
+ result2 = {
1447
1447
  meta: fullResult.meta,
1448
1448
  propsSummary: component.propsSummary,
1449
1449
  metadata: component.metadata
1450
1450
  };
1451
1451
  } else if (verbosity === "full") {
1452
- result = resolvedFields && resolvedFields.length > 0 ? projectFields(fullResult, resolvedFields) : fullResult;
1452
+ result2 = resolvedFields && resolvedFields.length > 0 ? projectFields(fullResult, resolvedFields) : fullResult;
1453
1453
  } else if (resolvedFields && resolvedFields.length > 0) {
1454
- result = projectFields(fullResult, resolvedFields);
1454
+ result2 = projectFields(fullResult, resolvedFields);
1455
1455
  } else {
1456
1456
  const { source: _source, ...withoutSource } = fullResult;
1457
- result = withoutSource;
1457
+ result2 = withoutSource;
1458
1458
  }
1459
1459
  return {
1460
- content: [{ type: "text", text: JSON.stringify(result) }]
1460
+ content: [{ type: "text", text: JSON.stringify(result2) }]
1461
1461
  };
1462
1462
  };
1463
1463
 
@@ -1876,45 +1876,45 @@ var renderHandler = async (args, ctx) => {
1876
1876
  }
1877
1877
  if (figmaUrl) {
1878
1878
  try {
1879
- const result = await compareComponent(viewerUrl, {
1879
+ const result2 = await compareComponent(viewerUrl, {
1880
1880
  component: componentName,
1881
1881
  variant: variantName,
1882
1882
  props,
1883
1883
  figmaUrl,
1884
1884
  threshold
1885
1885
  }, ctx.config.fileConfig?.endpoints);
1886
- if (result.error) {
1886
+ if (result2.error) {
1887
1887
  return {
1888
1888
  content: [{
1889
1889
  type: "text",
1890
- text: `Compare error: ${result.error}${result.suggestion ? `
1891
- Suggestion: ${result.suggestion}` : ""}`
1890
+ text: `Compare error: ${result2.error}${result2.suggestion ? `
1891
+ Suggestion: ${result2.suggestion}` : ""}`
1892
1892
  }],
1893
1893
  isError: true
1894
1894
  };
1895
1895
  }
1896
1896
  const content = [];
1897
- const summaryText = result.match ? `MATCH: ${componentName} matches Figma design (${result.diffPercentage}% diff, threshold: ${result.threshold}%)` : `MISMATCH: ${componentName} differs from Figma design by ${result.diffPercentage}% (threshold: ${result.threshold}%)`;
1897
+ const summaryText = result2.match ? `MATCH: ${componentName} matches Figma design (${result2.diffPercentage}% diff, threshold: ${result2.threshold}%)` : `MISMATCH: ${componentName} differs from Figma design by ${result2.diffPercentage}% (threshold: ${result2.threshold}%)`;
1898
1898
  content.push({ type: "text", text: summaryText });
1899
- if (result.diff && !result.match) {
1899
+ if (result2.diff && !result2.match) {
1900
1900
  content.push({
1901
1901
  type: "image",
1902
- data: result.diff.replace("data:image/png;base64,", ""),
1902
+ data: result2.diff.replace("data:image/png;base64,", ""),
1903
1903
  mimeType: "image/png"
1904
1904
  });
1905
1905
  content.push({
1906
1906
  type: "text",
1907
- text: `Diff image above shows visual differences (red highlights). Changed regions: ${result.changedRegions?.length ?? 0}`
1907
+ text: `Diff image above shows visual differences (red highlights). Changed regions: ${result2.changedRegions?.length ?? 0}`
1908
1908
  });
1909
1909
  }
1910
1910
  content.push({
1911
1911
  type: "text",
1912
1912
  text: JSON.stringify({
1913
- match: result.match,
1914
- diffPercentage: result.diffPercentage,
1915
- threshold: result.threshold,
1916
- figmaUrl: result.figmaUrl,
1917
- changedRegions: result.changedRegions
1913
+ match: result2.match,
1914
+ diffPercentage: result2.diffPercentage,
1915
+ threshold: result2.threshold,
1916
+ figmaUrl: result2.figmaUrl,
1917
+ changedRegions: result2.changedRegions
1918
1918
  })
1919
1919
  });
1920
1920
  return { content };
@@ -1929,15 +1929,15 @@ Suggestion: ${result.suggestion}` : ""}`
1929
1929
  }
1930
1930
  }
1931
1931
  try {
1932
- const result = await renderComponent(viewerUrl, {
1932
+ const result2 = await renderComponent(viewerUrl, {
1933
1933
  component: componentName,
1934
1934
  props,
1935
1935
  variant: variantName,
1936
1936
  viewport: viewport ?? { width: 800, height: 600 }
1937
1937
  }, ctx.config.fileConfig?.endpoints);
1938
- if (result.error) {
1938
+ if (result2.error) {
1939
1939
  return {
1940
- content: [{ type: "text", text: `Render error: ${result.error}` }],
1940
+ content: [{ type: "text", text: `Render error: ${result2.error}` }],
1941
1941
  isError: true
1942
1942
  };
1943
1943
  }
@@ -1945,7 +1945,7 @@ Suggestion: ${result.suggestion}` : ""}`
1945
1945
  content: [
1946
1946
  {
1947
1947
  type: "image",
1948
- data: result.screenshot.replace("data:image/png;base64,", ""),
1948
+ data: result2.screenshot.replace("data:image/png;base64,", ""),
1949
1949
  mimeType: "image/png"
1950
1950
  },
1951
1951
  {
@@ -1992,16 +1992,16 @@ var fixHandler = async (args, ctx) => {
1992
1992
  };
1993
1993
  }
1994
1994
  try {
1995
- const result = await fixComponent(viewerUrl, {
1995
+ const result2 = await fixComponent(viewerUrl, {
1996
1996
  component: componentName,
1997
1997
  variant: variantName,
1998
1998
  fixType
1999
1999
  }, ctx.config.fileConfig?.endpoints);
2000
- if (result.error) {
2000
+ if (result2.error) {
2001
2001
  return {
2002
2002
  content: [{
2003
2003
  type: "text",
2004
- text: `Fix generation error: ${result.error}`
2004
+ text: `Fix generation error: ${result2.error}`
2005
2005
  }],
2006
2006
  isError: true
2007
2007
  };
@@ -2013,10 +2013,10 @@ var fixHandler = async (args, ctx) => {
2013
2013
  component: componentName,
2014
2014
  variant: variantName ?? "all",
2015
2015
  fixType,
2016
- patches: result.patches,
2017
- summary: result.summary,
2018
- patchCount: result.patches.length,
2019
- nextStep: result.patches.length > 0 ? `Apply patches using your editor or \`patch\` command, then run ${ctx.toolNames.render} to confirm fixes.` : void 0
2016
+ patches: result2.patches,
2017
+ summary: result2.summary,
2018
+ patchCount: result2.patches.length,
2019
+ nextStep: result2.patches.length > 0 ? `Apply patches using your editor or \`patch\` command, then run ${ctx.toolNames.render} to confirm fixes.` : void 0
2020
2020
  })
2021
2021
  }]
2022
2022
  };
@@ -2061,25 +2061,25 @@ var a11yHandler = async (args, ctx) => {
2061
2061
  };
2062
2062
  }
2063
2063
  try {
2064
- const result = await auditComponent(viewerUrl, {
2064
+ const result2 = await auditComponent(viewerUrl, {
2065
2065
  component: componentName,
2066
2066
  variant: variantName,
2067
2067
  standard,
2068
2068
  includeFixPatches
2069
2069
  }, ctx.config.fileConfig?.endpoints);
2070
- if (result.error) {
2070
+ if (result2.error) {
2071
2071
  return {
2072
2072
  content: [{
2073
2073
  type: "text",
2074
- text: `A11y audit error: ${result.error}`
2074
+ text: `A11y audit error: ${result2.error}`
2075
2075
  }],
2076
2076
  isError: true
2077
2077
  };
2078
2078
  }
2079
2079
  let nextStep;
2080
- if (result.emptyAudit) {
2080
+ if (result2.emptyAudit) {
2081
2081
  nextStep = `No testable elements found for ${variantName ? `variant "${variantName}"` : componentName}. The variant may not exist or renders no accessible content. Check available variants with ${ctx.toolNames.inspect}("${componentName}").`;
2082
- } else if (result.passed) {
2082
+ } else if (result2.passed) {
2083
2083
  nextStep = 'All accessibility checks passed. Consider running with standard: "AAA" for enhanced compliance.';
2084
2084
  } else {
2085
2085
  nextStep = `Fix the violations above, then re-run ${ctx.toolNames.a11y} to verify. Use ${ctx.toolNames.fix} for automated fixes.`;
@@ -2091,12 +2091,12 @@ var a11yHandler = async (args, ctx) => {
2091
2091
  component: componentName,
2092
2092
  variant: variantName ?? "all",
2093
2093
  standard,
2094
- totalViolations: result.results.reduce((sum, r) => sum + r.summary.total, 0),
2095
- variantsPassingAA: `${result.aaPercent}%`,
2096
- variantsPassingAAA: `${result.aaaPercent}%`,
2097
- passed: result.passed,
2098
- ...result.emptyAudit && { emptyAudit: true },
2099
- results: result.results,
2094
+ totalViolations: result2.results.reduce((sum, r) => sum + r.summary.total, 0),
2095
+ variantsPassingAA: `${result2.aaPercent}%`,
2096
+ variantsPassingAAA: `${result2.aaaPercent}%`,
2097
+ passed: result2.passed,
2098
+ ...result2.emptyAudit && { emptyAudit: true },
2099
+ results: result2.results,
2100
2100
  nextStep
2101
2101
  })
2102
2102
  }]
@@ -2207,12 +2207,12 @@ function handleGraphTool(args, serializedGraph, blocks, componentNames2) {
2207
2207
  const suggestion = closest ? ` Did you mean "${closest}"?` : "";
2208
2208
  return { text: JSON.stringify({ error: `Component "${args.component}" not found in graph.${suggestion}` }), isError: true };
2209
2209
  }
2210
- const result = engine.impact(args.component, args.maxDepth ?? 3);
2210
+ const result2 = engine.impact(args.component, args.maxDepth ?? 3);
2211
2211
  return {
2212
2212
  text: JSON.stringify({
2213
2213
  mode: "impact",
2214
- ...result,
2215
- summary: `Changing ${args.component} affects ${result.totalAffected} component(s) and ${result.affectedBlocks.length} block(s)`
2214
+ ...result2,
2215
+ summary: `Changing ${args.component} affects ${result2.totalAffected} component(s) and ${result2.affectedBlocks.length} block(s)`
2216
2216
  })
2217
2217
  };
2218
2218
  }
@@ -2220,14 +2220,14 @@ function handleGraphTool(args, serializedGraph, blocks, componentNames2) {
2220
2220
  if (!args.component || !args.target) {
2221
2221
  return { text: JSON.stringify({ error: "component and target are required for path mode" }), isError: true };
2222
2222
  }
2223
- const result = engine.path(args.component, args.target);
2223
+ const result2 = engine.path(args.component, args.target);
2224
2224
  return {
2225
2225
  text: JSON.stringify({
2226
2226
  mode: "path",
2227
2227
  from: args.component,
2228
2228
  to: args.target,
2229
- ...result,
2230
- edges: result.edges.map((e) => ({
2229
+ ...result2,
2230
+ edges: result2.edges.map((e) => ({
2231
2231
  source: e.source,
2232
2232
  target: e.target,
2233
2233
  type: e.type
@@ -2306,20 +2306,20 @@ var graphHandler = async (args, ctx) => {
2306
2306
  };
2307
2307
  const data = ctx.data;
2308
2308
  const allNames = componentNames(data.snapshot);
2309
- const result = handleGraphTool(
2309
+ const result2 = handleGraphTool(
2310
2310
  graphArgs,
2311
2311
  data.graph,
2312
2312
  data.blocks,
2313
2313
  allNames
2314
2314
  );
2315
- if (result.isError) {
2315
+ if (result2.isError) {
2316
2316
  return {
2317
- content: [{ type: "text", text: result.text }],
2317
+ content: [{ type: "text", text: result2.text }],
2318
2318
  isError: true
2319
2319
  };
2320
2320
  }
2321
2321
  return {
2322
- content: [{ type: "text", text: result.text }]
2322
+ content: [{ type: "text", text: result2.text }]
2323
2323
  };
2324
2324
  };
2325
2325
 
@@ -2389,13 +2389,274 @@ var perfHandler = async (args, ctx) => {
2389
2389
  };
2390
2390
  };
2391
2391
 
2392
+ // src/tools/spec-govern.ts
2393
+ var SEVERITY_WEIGHTS = {
2394
+ critical: 10,
2395
+ serious: 5,
2396
+ moderate: 2,
2397
+ minor: 1
2398
+ };
2399
+ function isRuleEnabled(rule, defaultEnabled = true) {
2400
+ if (rule === void 0) return defaultEnabled;
2401
+ if (typeof rule === "boolean") return rule;
2402
+ return rule.enabled ?? defaultEnabled;
2403
+ }
2404
+ function ruleSeverity(rule, fallback) {
2405
+ return typeof rule === "object" && rule?.severity ? rule.severity : fallback;
2406
+ }
2407
+ function ruleOptions(rule) {
2408
+ return typeof rule === "object" && rule?.options ? rule.options : {};
2409
+ }
2410
+ function nodeId(node, fallback) {
2411
+ return typeof node.id === "string" ? node.id : `node-${fallback}`;
2412
+ }
2413
+ function nodeType(node) {
2414
+ return typeof node.type === "string" ? node.type : "_unknown";
2415
+ }
2416
+ function parentType(type) {
2417
+ const dotIndex = type.indexOf(".");
2418
+ return dotIndex > 0 ? type.slice(0, dotIndex) : type;
2419
+ }
2420
+ function walkNodes(nodes, visitor) {
2421
+ if (!Array.isArray(nodes)) return;
2422
+ for (const [index, node] of nodes.entries()) {
2423
+ if (typeof node !== "object" || node === null || Array.isArray(node)) {
2424
+ continue;
2425
+ }
2426
+ const specNode = node;
2427
+ visitor(specNode, index);
2428
+ walkNodes(specNode.children, visitor);
2429
+ }
2430
+ }
2431
+ function flattenNodes(spec) {
2432
+ const flattened = [];
2433
+ walkNodes(spec.nodes, (node) => flattened.push(node));
2434
+ return flattened;
2435
+ }
2436
+ function worstSeverity(violations) {
2437
+ const order = ["critical", "serious", "moderate", "minor"];
2438
+ return violations.reduce(
2439
+ (worst, violation) => order.indexOf(violation.severity) < order.indexOf(worst) ? violation.severity : worst,
2440
+ "minor"
2441
+ );
2442
+ }
2443
+ function result(validator, violations) {
2444
+ return {
2445
+ validator,
2446
+ severity: violations.length > 0 ? worstSeverity(violations) : "minor",
2447
+ passed: violations.length === 0,
2448
+ violations
2449
+ };
2450
+ }
2451
+ function computeScore(violations) {
2452
+ const penalty = violations.reduce(
2453
+ (sum, violation) => sum + SEVERITY_WEIGHTS[violation.severity],
2454
+ 0
2455
+ );
2456
+ return Math.max(0, 100 - penalty);
2457
+ }
2458
+ function validateComponents(nodes, options) {
2459
+ const rules = options.policy?.rules ?? {};
2460
+ const allowRule = rules["components/allow"];
2461
+ const denyRule = rules["components/deny"];
2462
+ const allowOptions = ruleOptions(allowRule);
2463
+ const denyOptions = ruleOptions(denyRule);
2464
+ const allowed = new Set(
2465
+ (allowOptions.components ?? options.allowedComponents).filter(Boolean)
2466
+ );
2467
+ const denied = new Set(denyOptions.components ?? []);
2468
+ const violations = [];
2469
+ for (const [index, node] of nodes.entries()) {
2470
+ const type = nodeType(node);
2471
+ const parent = parentType(type);
2472
+ if (allowed.size > 0 && isRuleEnabled(allowRule) && !allowed.has(type) && !allowed.has(parent)) {
2473
+ violations.push({
2474
+ nodeId: nodeId(node, index),
2475
+ nodeType: type,
2476
+ rule: "components/allow",
2477
+ severity: ruleSeverity(allowRule, "serious"),
2478
+ message: `Component "${type}" is not in the allowed list`,
2479
+ suggestion: `Use one of the allowed components: ${[...allowed].slice(0, 10).join(", ")}${allowed.size > 10 ? "..." : ""}`
2480
+ });
2481
+ }
2482
+ if (denied.size > 0 && isRuleEnabled(denyRule) && (denied.has(type) || denied.has(parent))) {
2483
+ violations.push({
2484
+ nodeId: nodeId(node, index),
2485
+ nodeType: type,
2486
+ rule: "components/deny",
2487
+ severity: ruleSeverity(denyRule, "serious"),
2488
+ message: `Component "${type}" is blocked by the deny list`,
2489
+ suggestion: `Remove "${type}" or replace it with an allowed alternative`
2490
+ });
2491
+ }
2492
+ }
2493
+ return result("components", violations);
2494
+ }
2495
+ function isEventHandlerProp(prop) {
2496
+ return /^on[A-Z]/.test(prop);
2497
+ }
2498
+ function validateSafety(nodes) {
2499
+ const violations = [];
2500
+ const dangerousProps = /* @__PURE__ */ new Set(["dangerouslySetInnerHTML", "innerHTML"]);
2501
+ for (const [index, node] of nodes.entries()) {
2502
+ for (const [prop, value] of Object.entries(node.props ?? {})) {
2503
+ if (dangerousProps.has(prop)) {
2504
+ violations.push({
2505
+ nodeId: nodeId(node, index),
2506
+ nodeType: nodeType(node),
2507
+ rule: "safety/no-dangerous-props",
2508
+ severity: "critical",
2509
+ message: `Dangerous prop "${prop}" is not allowed`,
2510
+ suggestion: `Remove "${prop}" or use a safe rendering pattern`,
2511
+ prop
2512
+ });
2513
+ }
2514
+ if (isEventHandlerProp(prop) && typeof value === "string") {
2515
+ violations.push({
2516
+ nodeId: nodeId(node, index),
2517
+ nodeType: nodeType(node),
2518
+ rule: "safety/no-string-handlers",
2519
+ severity: "serious",
2520
+ message: `Event handler prop "${prop}" must not be a string`,
2521
+ suggestion: `Remove "${prop}" from generated specs`,
2522
+ prop
2523
+ });
2524
+ }
2525
+ }
2526
+ }
2527
+ return result("safety", violations);
2528
+ }
2529
+ function hasHardcodedCssValue(value) {
2530
+ if (value.includes("var(")) return false;
2531
+ return /#[0-9a-f]{3,8}\b/i.test(value) || /\b\d+(?:px|rem|em)\b/.test(value);
2532
+ }
2533
+ function validateTokens(nodes, options) {
2534
+ const rules = options.policy?.rules ?? {};
2535
+ const requireRule = rules["tokens/require-design-tokens"];
2536
+ const prefixRule = rules["tokens/allowed-prefixes"];
2537
+ const requireTokens = isRuleEnabled(requireRule);
2538
+ const defaultTokenPrefix = options.tokenPrefix?.replace(/^--/, "");
2539
+ const allowedPrefixes = ruleOptions(prefixRule).prefixes ?? (defaultTokenPrefix ? [defaultTokenPrefix] : []);
2540
+ const violations = [];
2541
+ for (const [index, node] of nodes.entries()) {
2542
+ for (const [prop, value] of Object.entries(node.props ?? {})) {
2543
+ if (typeof value !== "string") continue;
2544
+ if (requireTokens && hasHardcodedCssValue(value)) {
2545
+ violations.push({
2546
+ nodeId: nodeId(node, index),
2547
+ nodeType: nodeType(node),
2548
+ rule: "tokens/require-design-tokens",
2549
+ severity: ruleSeverity(requireRule, "moderate"),
2550
+ message: `Hardcoded CSS value "${value}" in prop "${prop}" - use a design token instead`,
2551
+ suggestion: "Use a design token instead of a hardcoded CSS value",
2552
+ prop,
2553
+ rawValue: value
2554
+ });
2555
+ }
2556
+ if (allowedPrefixes.length > 0 && isRuleEnabled(prefixRule, Boolean(options.tokenPrefix))) {
2557
+ const matches = value.matchAll(/var\(--([^)]+)\)/g);
2558
+ for (const match of matches) {
2559
+ const tokenName = match[1];
2560
+ if (allowedPrefixes.some((prefix) => tokenName.startsWith(prefix))) {
2561
+ continue;
2562
+ }
2563
+ violations.push({
2564
+ nodeId: nodeId(node, index),
2565
+ nodeType: nodeType(node),
2566
+ rule: "tokens/allowed-prefixes",
2567
+ severity: ruleSeverity(prefixRule, "moderate"),
2568
+ message: `Token "--${tokenName}" does not use an allowed prefix (${allowedPrefixes.join(", ")})`,
2569
+ suggestion: `Use a token with one of the allowed prefixes: ${allowedPrefixes.map((prefix) => `--${prefix}*`).join(", ")}`,
2570
+ prop,
2571
+ rawValue: value
2572
+ });
2573
+ }
2574
+ }
2575
+ }
2576
+ }
2577
+ return result("tokens", violations);
2578
+ }
2579
+ function textFromChildren(children) {
2580
+ if (!Array.isArray(children)) return "";
2581
+ return children.map((child) => {
2582
+ if (typeof child === "string") return child;
2583
+ if (typeof child === "object" && child !== null && !Array.isArray(child)) {
2584
+ return textFromChildren(child.children);
2585
+ }
2586
+ return "";
2587
+ }).join(" ").trim();
2588
+ }
2589
+ function validateA11y(nodes) {
2590
+ const violations = [];
2591
+ for (const [index, node] of nodes.entries()) {
2592
+ const type = nodeType(node);
2593
+ if (!/button/i.test(type)) continue;
2594
+ const props = node.props ?? {};
2595
+ const label = props["aria-label"] ?? props["aria-labelledby"] ?? props.title;
2596
+ const childText = textFromChildren(node.children);
2597
+ if (typeof label === "string" && label.trim().length > 0) continue;
2598
+ if (childText.length > 0) continue;
2599
+ violations.push({
2600
+ nodeId: nodeId(node, index),
2601
+ nodeType: type,
2602
+ rule: "button-name",
2603
+ severity: "serious",
2604
+ message: "Buttons must have discernible text (button-name)",
2605
+ suggestion: "Add visible text or an aria-label"
2606
+ });
2607
+ }
2608
+ return result("a11y", violations);
2609
+ }
2610
+ function runSpecGovern(spec, options) {
2611
+ const startedAt = Date.now();
2612
+ const nodes = flattenNodes(spec);
2613
+ const results = [
2614
+ validateSafety(nodes),
2615
+ validateComponents(nodes, options),
2616
+ validateTokens(nodes, options),
2617
+ validateA11y(nodes)
2618
+ ];
2619
+ const violations = results.flatMap((entry) => entry.violations);
2620
+ const componentTypes = Array.from(
2621
+ new Set(nodes.map((node) => nodeType(node)))
2622
+ );
2623
+ return {
2624
+ passed: results.every((entry) => entry.passed),
2625
+ score: computeScore(violations),
2626
+ results,
2627
+ metadata: {
2628
+ runner: "mcp",
2629
+ duration: Date.now() - startedAt,
2630
+ nodeCount: nodes.length,
2631
+ componentTypes
2632
+ }
2633
+ };
2634
+ }
2635
+ function formatVerdict(verdict, format = "summary") {
2636
+ if (format === "json") {
2637
+ return JSON.stringify(verdict, null, 2);
2638
+ }
2639
+ const lines = [];
2640
+ const icon = verdict.passed ? "ok" : "fail";
2641
+ lines.push(`${icon} Governance check: score ${verdict.score}/100`);
2642
+ lines.push("");
2643
+ for (const entry of verdict.results) {
2644
+ const resultIcon = entry.passed ? "ok" : "fail";
2645
+ const count = entry.violations.length;
2646
+ lines.push(` ${resultIcon} ${entry.validator}: ${count === 0 ? "passed" : `${count} violation(s)`}`);
2647
+ for (const violation of entry.violations) {
2648
+ lines.push(` - [${violation.severity}] ${violation.nodeType}#${violation.nodeId}: ${violation.message}`);
2649
+ if (violation.suggestion) {
2650
+ lines.push(` -> ${violation.suggestion}`);
2651
+ }
2652
+ }
2653
+ }
2654
+ lines.push("");
2655
+ lines.push(`Duration: ${verdict.metadata.duration.toFixed(0)}ms | Nodes: ${verdict.metadata.nodeCount}`);
2656
+ return lines.join("\n");
2657
+ }
2658
+
2392
2659
  // src/tools/govern.ts
2393
- import {
2394
- handleGovernTool,
2395
- formatVerdict,
2396
- universal,
2397
- fragments as fragmentsPreset
2398
- } from "@fragments-sdk/govern";
2399
2660
  var governHandler = async (args, ctx) => {
2400
2661
  const spec = args?.spec;
2401
2662
  if (!spec || typeof spec !== "object") {
@@ -2416,23 +2677,12 @@ var governHandler = async (args, ctx) => {
2416
2677
  const allowedComponents = Object.values(ctx.data.components).map(
2417
2678
  (component) => component.name
2418
2679
  );
2419
- const tokenPrefix = ctx.data.tokens?.prefix;
2420
- const basePolicy = tokenPrefix === "fui-" ? { rules: fragmentsPreset().rules } : { rules: universal().rules };
2421
- basePolicy.rules["components/allow"] = {
2422
- enabled: true,
2423
- severity: "serious",
2424
- options: {
2425
- components: allowedComponents
2426
- }
2427
- };
2428
- const engineOptions = ctx.data.tokens ? { tokenData: ctx.data.tokens } : void 0;
2429
- const input = {
2430
- spec,
2431
- policy: policyOverrides,
2432
- format
2433
- };
2434
2680
  try {
2435
- const verdict = await handleGovernTool(input, basePolicy, engineOptions);
2681
+ const verdict = runSpecGovern(spec, {
2682
+ allowedComponents,
2683
+ tokenPrefix: ctx.data.tokens?.prefix,
2684
+ policy: policyOverrides
2685
+ });
2436
2686
  const text = format === "summary" ? formatVerdict(verdict, "summary") : JSON.stringify(verdict);
2437
2687
  return {
2438
2688
  content: [{ type: "text", text }],
@@ -2463,32 +2713,80 @@ var governHandler = async (args, ctx) => {
2463
2713
  };
2464
2714
 
2465
2715
  // src/tools/validate-and-fix.ts
2466
- import {
2467
- formatVerdict as formatVerdict2,
2468
- fragments as fragmentsPreset2,
2469
- handleGovernTool as handleGovernTool2,
2470
- universal as universal2
2471
- } from "@fragments-sdk/govern";
2472
2716
  function classifyComponent(component) {
2473
2717
  if (component.status === "deprecated") return "discouraged";
2474
2718
  if (component.isCanonical && component.tier === "core") return "preferred";
2475
2719
  return "allowed";
2476
2720
  }
2477
2721
  function buildEffectiveComponents(ctx) {
2478
- const selectionByKey = new Map(
2722
+ const validateFixByKey = new Map(
2479
2723
  (ctx.data.validateFixContext?.components ?? []).map((component) => [
2480
2724
  component.componentKey,
2481
- component.selection
2725
+ component
2482
2726
  ])
2483
2727
  );
2484
2728
  return Object.entries(ctx.data.components).map(([componentKey, component]) => ({
2485
2729
  component,
2486
- selection: selectionByKey.get(componentKey) ?? classifyComponent(component)
2730
+ selection: validateFixByKey.get(componentKey)?.selection ?? classifyComponent(component),
2731
+ isActive: validateFixByKey.get(componentKey)?.isActive ?? true,
2732
+ reasons: validateFixByKey.get(componentKey)?.reasons ?? []
2487
2733
  }));
2488
2734
  }
2489
2735
  function cloneSpec(spec) {
2490
2736
  return structuredClone(spec);
2491
2737
  }
2738
+ function isPlainObject(value) {
2739
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2740
+ }
2741
+ function validateNodeShape(node, path) {
2742
+ if (!isPlainObject(node)) {
2743
+ return `${path} must be an object`;
2744
+ }
2745
+ if ("id" in node && node.id !== void 0 && typeof node.id !== "string") {
2746
+ return `${path}.id must be a string`;
2747
+ }
2748
+ if ("type" in node && node.type !== void 0 && typeof node.type !== "string") {
2749
+ return `${path}.type must be a string`;
2750
+ }
2751
+ if ("props" in node && node.props !== void 0 && !isPlainObject(node.props)) {
2752
+ return `${path}.props must be an object`;
2753
+ }
2754
+ if ("children" in node && node.children !== void 0) {
2755
+ if (!Array.isArray(node.children)) {
2756
+ return `${path}.children must be an array`;
2757
+ }
2758
+ for (const [index, child] of node.children.entries()) {
2759
+ if (!isPlainObject(child)) continue;
2760
+ const childError = validateNodeShape(
2761
+ child,
2762
+ `${path}.children[${index}]`
2763
+ );
2764
+ if (childError) return childError;
2765
+ }
2766
+ }
2767
+ return null;
2768
+ }
2769
+ function validateSpecShape(spec) {
2770
+ if (!isPlainObject(spec)) {
2771
+ return "spec must be an object";
2772
+ }
2773
+ if ("root" in spec && spec.root !== void 0 && typeof spec.root !== "string") {
2774
+ return "spec.root must be a string";
2775
+ }
2776
+ if ("metadata" in spec && spec.metadata !== void 0 && !isPlainObject(spec.metadata)) {
2777
+ return "spec.metadata must be an object";
2778
+ }
2779
+ if ("nodes" in spec && spec.nodes !== void 0) {
2780
+ if (!Array.isArray(spec.nodes)) {
2781
+ return "spec.nodes must be an array";
2782
+ }
2783
+ for (const [index, node] of spec.nodes.entries()) {
2784
+ const error = validateNodeShape(node, `spec.nodes[${index}]`);
2785
+ if (error) return error;
2786
+ }
2787
+ }
2788
+ return null;
2789
+ }
2492
2790
  function getSelectionNames(effectiveComponents, selection) {
2493
2791
  const selections = Array.isArray(selection) ? selection : [selection];
2494
2792
  return Array.from(
@@ -2497,27 +2795,146 @@ function getSelectionNames(effectiveComponents, selection) {
2497
2795
  )
2498
2796
  );
2499
2797
  }
2500
- function buildBasePolicy(ctx) {
2501
- const tokenPrefix = ctx.data.tokens?.prefix;
2502
- const basePolicy = tokenPrefix === "fui-" ? { rules: fragmentsPreset2().rules } : { rules: universal2().rules };
2503
- const allowedComponents = buildEffectiveComponents(ctx).filter(({ selection }) => selection === "preferred" || selection === "allowed").map(({ component }) => component.name);
2504
- basePolicy.rules["components/allow"] = {
2505
- enabled: true,
2506
- severity: "serious",
2507
- options: {
2508
- components: allowedComponents
2798
+ function normalizeTokens(value) {
2799
+ return value.toLowerCase().split(/[^a-z0-9]+/g).map((token) => token.trim()).filter((token) => token.length >= 3);
2800
+ }
2801
+ function buildComponentText(component) {
2802
+ return [
2803
+ component.name,
2804
+ component.category,
2805
+ component.description,
2806
+ component.guidance.usageGuidance,
2807
+ ...component.guidance.when,
2808
+ ...component.guidance.whenNot,
2809
+ ...component.guidance.dos,
2810
+ ...component.guidance.donts
2811
+ ].filter(Boolean).join(" ");
2812
+ }
2813
+ function rankReplacementCandidates(args) {
2814
+ const nodePropKeys = Object.keys(args.node.props ?? {});
2815
+ const sourcePropKeys = new Set(Object.keys(args.source.component.props ?? {}));
2816
+ const sourceTokens = new Set(normalizeTokens(buildComponentText(args.source.component)));
2817
+ return args.candidates.map((candidate) => {
2818
+ let score = 0;
2819
+ const reasons = [];
2820
+ const candidatePropKeys = new Set(Object.keys(candidate.component.props ?? {}));
2821
+ if (candidate.selection === "preferred") {
2822
+ score += 12;
2823
+ reasons.push("preferred selection");
2824
+ }
2825
+ if (candidate.component.isCanonical) {
2826
+ score += 6;
2827
+ reasons.push("canonical component");
2828
+ }
2829
+ if (candidate.component.tier === "core") {
2830
+ score += 4;
2831
+ reasons.push("core tier");
2832
+ }
2833
+ if (candidate.isActive) {
2834
+ score += 3;
2835
+ reasons.push("active component");
2836
+ }
2837
+ const supportedNodeProps = nodePropKeys.filter(
2838
+ (key) => candidatePropKeys.has(key)
2839
+ );
2840
+ if (supportedNodeProps.length > 0) {
2841
+ score += supportedNodeProps.length * 5;
2842
+ reasons.push(`supports node props: ${supportedNodeProps.join(", ")}`);
2509
2843
  }
2510
- };
2511
- return basePolicy;
2844
+ const missingNodeProps = nodePropKeys.filter(
2845
+ (key) => !candidatePropKeys.has(key)
2846
+ );
2847
+ if (missingNodeProps.length > 0) {
2848
+ score -= missingNodeProps.length * 6;
2849
+ reasons.push(`missing node props: ${missingNodeProps.join(", ")}`);
2850
+ }
2851
+ const sharedContractProps = [...sourcePropKeys].filter(
2852
+ (key) => candidatePropKeys.has(key)
2853
+ );
2854
+ if (sharedContractProps.length > 0) {
2855
+ score += sharedContractProps.length * 2;
2856
+ reasons.push(`shares source props: ${sharedContractProps.join(", ")}`);
2857
+ }
2858
+ const candidateTokens = new Set(
2859
+ normalizeTokens(buildComponentText(candidate.component))
2860
+ );
2861
+ const tokenOverlap = [...candidateTokens].filter(
2862
+ (token) => sourceTokens.has(token)
2863
+ );
2864
+ if (tokenOverlap.length > 0) {
2865
+ score += Math.min(tokenOverlap.length, 4);
2866
+ reasons.push(`guidance overlap: ${tokenOverlap.join(", ")}`);
2867
+ }
2868
+ if (candidate.component.parentComponentId && candidate.component.parentComponentId === args.source.component.parentComponentId) {
2869
+ score += 2;
2870
+ reasons.push("matches parent component");
2871
+ }
2872
+ return {
2873
+ name: candidate.component.name,
2874
+ score,
2875
+ reasons
2876
+ };
2877
+ }).sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
2512
2878
  }
2513
- async function runGovern(spec, ctx, policyOverrides) {
2514
- const input = {
2515
- spec,
2516
- policy: policyOverrides,
2517
- format: "json"
2518
- };
2519
- const engineOptions = ctx.data.tokens ? { tokenData: ctx.data.tokens } : void 0;
2520
- return handleGovernTool2(input, buildBasePolicy(ctx), engineOptions);
2879
+ function chooseDeterministicCandidate(rankedCandidates) {
2880
+ if (rankedCandidates.length === 0) return null;
2881
+ if (rankedCandidates.length === 1) return rankedCandidates[0].name;
2882
+ const [first, second] = rankedCandidates;
2883
+ if (first.score <= 0) return null;
2884
+ if (first.score - second.score < 4) return null;
2885
+ return first.name;
2886
+ }
2887
+ function countViolations(verdict) {
2888
+ return verdict.results.reduce(
2889
+ (sum, result2) => sum + result2.violations.length,
2890
+ 0
2891
+ );
2892
+ }
2893
+ function getNextAction(status, replacements, unresolvedAmbiguityCount) {
2894
+ if (status === "pass") return "none";
2895
+ if (status === "fixed") return "apply_fixed_spec";
2896
+ if (status === "partial_fix") return "review_partial_fix";
2897
+ if (unresolvedAmbiguityCount > 0) return "resolve_ambiguities";
2898
+ if (replacements.length > 0) return "review_partial_fix";
2899
+ return "revise_input";
2900
+ }
2901
+ async function emitValidateAndFixTelemetry(args) {
2902
+ if (!args.ctx.mcp?.server) return;
2903
+ try {
2904
+ await args.ctx.mcp.server.sendLoggingMessage({
2905
+ level: "info",
2906
+ data: JSON.stringify({
2907
+ tool: "validate_and_fix",
2908
+ status: args.status,
2909
+ nextAction: args.nextAction,
2910
+ resolutionPath: args.resolutionPath,
2911
+ evaluation: args.evaluation,
2912
+ attestation: args.attestation
2913
+ }),
2914
+ logger: "fragments-mcp"
2915
+ });
2916
+ } catch {
2917
+ }
2918
+ }
2919
+ function buildAllowedComponents(ctx) {
2920
+ return buildEffectiveComponents(ctx).filter(({ selection }) => selection === "preferred" || selection === "allowed").map(({ component }) => component.name);
2921
+ }
2922
+ function runGovern(spec, ctx, policyOverrides) {
2923
+ return runSpecGovern(spec, {
2924
+ allowedComponents: buildAllowedComponents(ctx),
2925
+ tokenPrefix: ctx.data.tokens?.prefix,
2926
+ policy: policyOverrides
2927
+ });
2928
+ }
2929
+ function walkNodes2(nodes, visitor, path = "nodes") {
2930
+ for (const [index, node] of nodes.entries()) {
2931
+ if (!isPlainObject(node)) continue;
2932
+ const nodeRef = `${path}[${index}]`;
2933
+ visitor(node, nodeRef);
2934
+ if (Array.isArray(node.children)) {
2935
+ walkNodes2(node.children, visitor, `${nodeRef}.children`);
2936
+ }
2937
+ }
2521
2938
  }
2522
2939
  function applyDeterministicReplacements(spec, ctx) {
2523
2940
  const effectiveComponents = buildEffectiveComponents(ctx);
@@ -2538,39 +2955,241 @@ function applyDeterministicReplacements(spec, ctx) {
2538
2955
  const fixedSpec = cloneSpec(spec);
2539
2956
  const nodes = Array.isArray(fixedSpec.nodes) ? fixedSpec.nodes : [];
2540
2957
  const replacements = [];
2541
- for (const node of nodes) {
2542
- if (!node.type) continue;
2958
+ const ambiguities = [];
2959
+ walkNodes2(nodes, (node, nodeRef) => {
2960
+ if (!node.type) return;
2543
2961
  const componentEntries = byName.get(node.type) ?? [];
2544
2962
  const component = componentEntries[0];
2545
2963
  if (!component || component.selection !== "discouraged" && component.selection !== "forbidden") {
2546
- continue;
2964
+ return;
2965
+ }
2966
+ const candidateEntries = (preferredByCategory.get(component.component.category ?? "uncategorized") ?? []).filter((candidate) => candidate !== component.component.name).map(
2967
+ (candidateName) => effectiveComponents.find(
2968
+ (entry) => entry.component.name === candidateName
2969
+ )
2970
+ ).filter((entry) => Boolean(entry));
2971
+ const rankedCandidates = rankReplacementCandidates({
2972
+ source: component,
2973
+ candidates: candidateEntries,
2974
+ node
2975
+ });
2976
+ const replacement = chooseDeterministicCandidate(rankedCandidates);
2977
+ if (!replacement) {
2978
+ if (rankedCandidates.length > 1) {
2979
+ ambiguities.push({
2980
+ nodeId: node.id ?? node.type,
2981
+ nodeRef,
2982
+ from: component.component.name,
2983
+ candidates: rankedCandidates.map((candidate) => candidate.name),
2984
+ rankedCandidates,
2985
+ reason: "ambiguous_preferred_components"
2986
+ });
2987
+ }
2988
+ return;
2547
2989
  }
2548
- const candidates = (preferredByCategory.get(component.component.category ?? "uncategorized") ?? []).filter((candidate) => candidate !== component.component.name);
2549
- if (candidates.length !== 1) continue;
2550
- const replacement = candidates[0];
2551
2990
  replacements.push({
2552
2991
  nodeId: node.id ?? node.type,
2553
2992
  from: component.component.name,
2554
2993
  to: replacement,
2555
- reason: "deprecated_component"
2994
+ reason: "deprecated_component",
2995
+ mode: "deterministic"
2556
2996
  });
2557
2997
  node.type = replacement;
2558
- }
2998
+ });
2559
2999
  return {
2560
3000
  fixedSpec,
2561
- replacements
3001
+ replacements,
3002
+ ambiguities
2562
3003
  };
2563
3004
  }
3005
+ function applyReplacementAtRef(spec, nodeRef, replacement) {
3006
+ const nodes = Array.isArray(spec.nodes) ? spec.nodes : [];
3007
+ let applied = false;
3008
+ walkNodes2(nodes, (node, currentRef) => {
3009
+ if (applied || currentRef !== nodeRef) return;
3010
+ node.type = replacement;
3011
+ applied = true;
3012
+ });
3013
+ return applied;
3014
+ }
3015
+ function supportsFormElicitation(ctx) {
3016
+ const capabilities = ctx.mcp?.clientCapabilities?.elicitation;
3017
+ if (!capabilities) return false;
3018
+ return capabilities.form !== void 0 || capabilities.url === void 0;
3019
+ }
3020
+ function supportsSampling(ctx) {
3021
+ return Boolean(ctx.mcp?.clientCapabilities?.sampling);
3022
+ }
3023
+ function getSamplingText(response) {
3024
+ if (Array.isArray(response.content)) {
3025
+ return response.content.find((item) => item.type === "text")?.text;
3026
+ }
3027
+ return response.content.type === "text" ? response.content.text : void 0;
3028
+ }
3029
+ function buildComponentGuidanceMap(ctx) {
3030
+ return new Map(
3031
+ Object.values(ctx.data.components).map((component) => {
3032
+ const guidance = [];
3033
+ if (component.description) {
3034
+ guidance.push(`description: ${component.description}`);
3035
+ }
3036
+ if (component.guidance.usageGuidance) {
3037
+ guidance.push(`usage: ${component.guidance.usageGuidance}`);
3038
+ }
3039
+ if (component.guidance.dos.length > 0) {
3040
+ guidance.push(`dos: ${component.guidance.dos.join("; ")}`);
3041
+ }
3042
+ if (component.guidance.donts.length > 0) {
3043
+ guidance.push(`donts: ${component.guidance.donts.join("; ")}`);
3044
+ }
3045
+ return [component.name, guidance];
3046
+ })
3047
+ );
3048
+ }
3049
+ function buildAmbiguityGuidanceLines(candidates, guidanceByName) {
3050
+ return candidates.map((candidate) => {
3051
+ const guidance = guidanceByName.get(candidate) ?? [];
3052
+ return guidance.length > 0 ? `${candidate}: ${guidance.join(" | ")}` : `${candidate}: no additional guidance available`;
3053
+ });
3054
+ }
3055
+ async function resolveAmbiguitiesWithElicitation(ambiguities, spec, ctx) {
3056
+ if (!ctx.mcp?.server || ambiguities.length === 0) return [];
3057
+ const replacements = [];
3058
+ const guidanceByName = buildComponentGuidanceMap(ctx);
3059
+ for (const ambiguity of ambiguities) {
3060
+ const guidanceLines = buildAmbiguityGuidanceLines(
3061
+ ambiguity.candidates,
3062
+ guidanceByName
3063
+ );
3064
+ const result2 = await ctx.mcp.server.elicitInput({
3065
+ mode: "form",
3066
+ message: [
3067
+ `Fragments found multiple safe replacements for ${ambiguity.from} on node ${ambiguity.nodeId}.`,
3068
+ "Choose the best replacement, or skip this change.",
3069
+ "Candidate guidance:",
3070
+ ...guidanceLines
3071
+ ].join("\n"),
3072
+ requestedSchema: {
3073
+ type: "object",
3074
+ properties: {
3075
+ replacement: {
3076
+ type: "string",
3077
+ title: "Replacement",
3078
+ description: [
3079
+ "Select the best replacement for this node.",
3080
+ ...guidanceLines,
3081
+ "Choose Skip to leave this component unchanged."
3082
+ ].join("\n"),
3083
+ oneOf: [
3084
+ ...ambiguity.candidates.map((candidate) => ({
3085
+ const: candidate,
3086
+ title: candidate
3087
+ })),
3088
+ {
3089
+ const: "__skip__",
3090
+ title: "Skip"
3091
+ }
3092
+ ],
3093
+ default: "__skip__"
3094
+ }
3095
+ },
3096
+ required: ["replacement"]
3097
+ }
3098
+ });
3099
+ if (result2.action !== "accept" || !result2.content || typeof result2.content.replacement !== "string" || result2.content.replacement === "__skip__") {
3100
+ continue;
3101
+ }
3102
+ if (!ambiguity.candidates.includes(result2.content.replacement)) {
3103
+ continue;
3104
+ }
3105
+ if (!applyReplacementAtRef(spec, ambiguity.nodeRef, result2.content.replacement)) {
3106
+ continue;
3107
+ }
3108
+ replacements.push({
3109
+ nodeId: ambiguity.nodeId,
3110
+ from: ambiguity.from,
3111
+ to: result2.content.replacement,
3112
+ reason: "deprecated_component",
3113
+ mode: "elicitation"
3114
+ });
3115
+ }
3116
+ return replacements;
3117
+ }
3118
+ async function resolveAmbiguitiesWithSampling(ambiguities, spec, ctx) {
3119
+ if (!ctx.mcp?.server || ambiguities.length === 0) return [];
3120
+ const guidanceByName = buildComponentGuidanceMap(ctx);
3121
+ const prompt = [
3122
+ "You are choosing design-system component replacements.",
3123
+ 'Return strict JSON only in the form {"choices":[{"nodeId":"...","replacement":"...|__skip__"}]}.',
3124
+ "Only choose from the provided candidates.",
3125
+ "Prefer the option whose guidance best matches the original component intent.",
3126
+ JSON.stringify(
3127
+ {
3128
+ ambiguities: ambiguities.map((ambiguity) => ({
3129
+ nodeId: ambiguity.nodeId,
3130
+ from: ambiguity.from,
3131
+ candidates: ambiguity.candidates.map((candidate) => ({
3132
+ name: candidate,
3133
+ guidance: guidanceByName.get(candidate) ?? []
3134
+ }))
3135
+ }))
3136
+ },
3137
+ null,
3138
+ 2
3139
+ )
3140
+ ].join("\n\n");
3141
+ const response = await ctx.mcp.server.createMessage({
3142
+ messages: [
3143
+ {
3144
+ role: "user",
3145
+ content: {
3146
+ type: "text",
3147
+ text: prompt
3148
+ }
3149
+ }
3150
+ ],
3151
+ maxTokens: 600
3152
+ });
3153
+ const text = getSamplingText(response);
3154
+ if (!text) return [];
3155
+ let parsed;
3156
+ try {
3157
+ parsed = JSON.parse(text);
3158
+ } catch {
3159
+ return [];
3160
+ }
3161
+ const replacements = [];
3162
+ for (const choice of parsed.choices ?? []) {
3163
+ if (!choice.nodeId || !choice.replacement || choice.replacement === "__skip__") {
3164
+ continue;
3165
+ }
3166
+ const ambiguity = ambiguities.find((entry) => entry.nodeId === choice.nodeId);
3167
+ if (!ambiguity || !ambiguity.candidates.includes(choice.replacement)) {
3168
+ continue;
3169
+ }
3170
+ if (!applyReplacementAtRef(spec, ambiguity.nodeRef, choice.replacement)) {
3171
+ continue;
3172
+ }
3173
+ replacements.push({
3174
+ nodeId: ambiguity.nodeId,
3175
+ from: ambiguity.from,
3176
+ to: choice.replacement,
3177
+ reason: "deprecated_component",
3178
+ mode: "sampling"
3179
+ });
3180
+ }
3181
+ return replacements;
3182
+ }
2564
3183
  function buildSummary(args) {
2565
3184
  const lines = [
2566
3185
  `status: ${args.status}`,
2567
- `original: ${formatVerdict2(args.originalVerdict, "summary")}`
3186
+ `original: ${formatVerdict(args.originalVerdict, "summary")}`
2568
3187
  ];
2569
3188
  if (args.replacements.length > 0) {
2570
3189
  lines.push(
2571
3190
  `replacements: ${args.replacements.map((item) => `${item.from} -> ${item.to}`).join(", ")}`
2572
3191
  );
2573
- lines.push(`final: ${formatVerdict2(args.finalVerdict, "summary")}`);
3192
+ lines.push(`final: ${formatVerdict(args.finalVerdict, "summary")}`);
2574
3193
  }
2575
3194
  return lines.join("\n");
2576
3195
  }
@@ -2589,43 +3208,166 @@ var validateAndFixHandler = async (args, ctx) => {
2589
3208
  isError: true
2590
3209
  };
2591
3210
  }
3211
+ const specError = validateSpecShape(spec);
3212
+ if (specError) {
3213
+ return {
3214
+ content: [
3215
+ {
3216
+ type: "text",
3217
+ text: JSON.stringify({
3218
+ error: `Invalid spec format: ${specError}. Expected: { nodes: [{ id?: string, type?: string, props?: object, children?: object[] }] }`
3219
+ })
3220
+ }
3221
+ ],
3222
+ isError: true
3223
+ };
3224
+ }
2592
3225
  const policyOverrides = args?.policy;
2593
3226
  const applyFixes = args?.applyFixes !== false;
3227
+ const allowElicitation = args?.allowElicitation !== false;
3228
+ const allowSampling = args?.allowSampling !== false;
2594
3229
  const format = args?.format ?? "json";
3230
+ const startedAt = Date.now();
2595
3231
  try {
2596
3232
  const effectiveComponents = buildEffectiveComponents(ctx);
2597
3233
  const originalVerdict = await runGovern(spec, ctx, policyOverrides);
2598
3234
  let finalVerdict = originalVerdict;
3235
+ let workingSpec;
2599
3236
  let fixedSpec;
2600
3237
  let replacements = [];
3238
+ let ambiguities = [];
3239
+ const resolutionPath = [];
2601
3240
  if (!originalVerdict.passed && applyFixes) {
2602
- const result = applyDeterministicReplacements(
3241
+ const result2 = applyDeterministicReplacements(
2603
3242
  spec,
2604
3243
  ctx
2605
3244
  );
2606
- replacements = result.replacements;
2607
- fixedSpec = result.fixedSpec;
3245
+ replacements = result2.replacements;
3246
+ ambiguities = result2.ambiguities;
2608
3247
  if (replacements.length > 0) {
3248
+ workingSpec = result2.fixedSpec;
3249
+ resolutionPath.push("deterministic");
3250
+ }
3251
+ const unresolvedAmbiguities2 = ambiguities.filter(
3252
+ (ambiguity) => !replacements.some((replacement) => replacement.nodeId === ambiguity.nodeId)
3253
+ );
3254
+ if (unresolvedAmbiguities2.length > 0 && allowElicitation && supportsFormElicitation(ctx)) {
3255
+ workingSpec ??= cloneSpec(spec);
3256
+ const elicitedReplacements = await resolveAmbiguitiesWithElicitation(
3257
+ unresolvedAmbiguities2,
3258
+ workingSpec,
3259
+ ctx
3260
+ );
3261
+ if (elicitedReplacements.length > 0) {
3262
+ resolutionPath.push("elicitation");
3263
+ }
3264
+ replacements.push(...elicitedReplacements);
3265
+ }
3266
+ const remainingAmbiguities = ambiguities.filter(
3267
+ (ambiguity) => !replacements.some((replacement) => replacement.nodeId === ambiguity.nodeId)
3268
+ );
3269
+ if (remainingAmbiguities.length > 0 && allowSampling && supportsSampling(ctx)) {
3270
+ workingSpec ??= cloneSpec(spec);
3271
+ const sampledReplacements = await resolveAmbiguitiesWithSampling(
3272
+ remainingAmbiguities,
3273
+ workingSpec,
3274
+ ctx
3275
+ );
3276
+ if (sampledReplacements.length > 0) {
3277
+ resolutionPath.push("sampling");
3278
+ }
3279
+ replacements.push(...sampledReplacements);
3280
+ }
3281
+ fixedSpec = replacements.length > 0 ? workingSpec : void 0;
3282
+ if (fixedSpec) {
2609
3283
  finalVerdict = await runGovern(fixedSpec, ctx, policyOverrides);
2610
3284
  }
2611
3285
  }
2612
- const status = originalVerdict.passed ? "pass" : replacements.length > 0 ? "fixed" : "fail";
3286
+ const status = originalVerdict.passed ? "pass" : replacements.length > 0 ? finalVerdict.passed ? "fixed" : "partial_fix" : "fail";
3287
+ const unresolvedAmbiguities = ambiguities.filter(
3288
+ (ambiguity) => !replacements.some((replacement) => replacement.nodeId === ambiguity.nodeId)
3289
+ ).map(({ nodeRef: _nodeRef, ...ambiguity }) => ambiguity);
3290
+ const attestation = {
3291
+ sourceType: ctx.data.snapshot.sourceType,
3292
+ sourceLabel: ctx.data.snapshot.sourceLabel,
3293
+ designSystemName: ctx.data.snapshot.metadata.designSystemName,
3294
+ packageName: ctx.data.snapshot.metadata.packageName,
3295
+ importPath: ctx.data.snapshot.metadata.importPath,
3296
+ catalogRevision: ctx.data.validateFixContext?.catalogRevision ?? ctx.data.snapshot.metadata.revision,
3297
+ catalogUpdatedAt: ctx.data.validateFixContext?.updatedAt ?? ctx.data.snapshot.metadata.updatedAt,
3298
+ policy: {
3299
+ mode: ctx.data.validateFixContext?.policy.mode ?? "embedded",
3300
+ endpoint: ctx.data.validateFixContext?.policy.endpoint,
3301
+ overrideApplied: Boolean(policyOverrides)
3302
+ },
3303
+ clientCapabilities: {
3304
+ sampling: supportsSampling(ctx),
3305
+ samplingTools: Boolean(ctx.mcp?.clientCapabilities?.sampling?.tools),
3306
+ elicitationForm: supportsFormElicitation(ctx),
3307
+ roots: Boolean(ctx.mcp?.clientCapabilities?.roots)
3308
+ },
3309
+ capabilitiesUsed: {
3310
+ deterministic: replacements.some((replacement) => replacement.mode === "deterministic"),
3311
+ elicitation: replacements.some((replacement) => replacement.mode === "elicitation"),
3312
+ sampling: replacements.some((replacement) => replacement.mode === "sampling")
3313
+ }
3314
+ };
3315
+ const evaluation = {
3316
+ durationMs: Date.now() - startedAt,
3317
+ originalViolationCount: countViolations(originalVerdict),
3318
+ finalViolationCount: countViolations(finalVerdict),
3319
+ originalPassed: originalVerdict.passed,
3320
+ finalPassed: finalVerdict.passed,
3321
+ replacementCount: replacements.length,
3322
+ replacementsByMode: {
3323
+ deterministic: replacements.filter((replacement) => replacement.mode === "deterministic").length,
3324
+ elicitation: replacements.filter((replacement) => replacement.mode === "elicitation").length,
3325
+ sampling: replacements.filter((replacement) => replacement.mode === "sampling").length
3326
+ },
3327
+ ambiguityCount: ambiguities.length,
3328
+ unresolvedAmbiguityCount: unresolvedAmbiguities.length,
3329
+ candidateRankingUsed: true
3330
+ };
3331
+ const nextAction = getNextAction(
3332
+ status,
3333
+ replacements,
3334
+ unresolvedAmbiguities.length
3335
+ );
2613
3336
  const payload = {
2614
3337
  status,
3338
+ nextAction,
2615
3339
  applyFixes,
3340
+ allowElicitation,
3341
+ allowSampling,
3342
+ resolutionPath,
2616
3343
  replacements,
2617
3344
  originalVerdict,
2618
3345
  finalVerdict,
2619
3346
  ...fixedSpec ? { fixedSpec } : {},
3347
+ attestation,
3348
+ evaluation,
2620
3349
  preferredComponents: getSelectionNames(
2621
3350
  effectiveComponents,
2622
3351
  "preferred"
2623
3352
  ),
2624
- discouragedComponents: getSelectionNames(effectiveComponents, [
2625
- "discouraged",
3353
+ discouragedComponents: getSelectionNames(
3354
+ effectiveComponents,
3355
+ "discouraged"
3356
+ ),
3357
+ forbiddenComponents: getSelectionNames(
3358
+ effectiveComponents,
2626
3359
  "forbidden"
2627
- ])
3360
+ ),
3361
+ unresolvedAmbiguities
2628
3362
  };
3363
+ await emitValidateAndFixTelemetry({
3364
+ ctx,
3365
+ status,
3366
+ resolutionPath,
3367
+ evaluation,
3368
+ attestation,
3369
+ nextAction
3370
+ });
2629
3371
  return {
2630
3372
  content: [
2631
3373
  {
@@ -2640,19 +3382,22 @@ var validateAndFixHandler = async (args, ctx) => {
2640
3382
  ],
2641
3383
  _meta: {
2642
3384
  status,
3385
+ nextAction,
2643
3386
  replacementCount: replacements.length,
2644
- passed: finalVerdict.passed
3387
+ passed: finalVerdict.passed,
3388
+ unresolvedAmbiguityCount: unresolvedAmbiguities.length,
3389
+ resolutionPath,
3390
+ catalogRevision: attestation.catalogRevision
2645
3391
  }
2646
3392
  };
2647
3393
  } catch (error) {
2648
3394
  const message = error instanceof Error ? error.message : String(error);
2649
- const isSpecError = message.includes("Expected") || message.includes("Required");
2650
3395
  return {
2651
3396
  content: [
2652
3397
  {
2653
3398
  type: "text",
2654
3399
  text: JSON.stringify({
2655
- error: isSpecError ? `Invalid spec format: ${message}. Expected: { nodes: [{ id: string, type: string, props: object, children?: string[] }] }` : message
3400
+ error: message
2656
3401
  })
2657
3402
  }
2658
3403
  ],
@@ -2764,10 +3509,10 @@ var findingsListHandler = async (args, ctx) => {
2764
3509
  if (args.filePath) params.filePath = String(args.filePath);
2765
3510
  if (args.limit != null) params.limit = Number(args.limit);
2766
3511
  try {
2767
- const result = await fetchFindings(apiKey, params, cloudUrl);
3512
+ const result2 = await fetchFindings(apiKey, params, cloudUrl);
2768
3513
  return {
2769
- content: [{ type: "text", text: JSON.stringify(result) }],
2770
- _meta: { count: result.findings.length }
3514
+ content: [{ type: "text", text: JSON.stringify(result2) }],
3515
+ _meta: { count: result2.findings.length }
2771
3516
  };
2772
3517
  } catch (error) {
2773
3518
  return {
@@ -2800,8 +3545,8 @@ var findingsForFileHandler = async (args, ctx) => {
2800
3545
  }
2801
3546
  const cloudUrl = resolveCloudUrl(ctx);
2802
3547
  try {
2803
- const result = await fetchFindingsForFile(apiKey, filePath, cloudUrl);
2804
- const findings = result.findings.filter((f) => f.filePath === filePath);
3548
+ const result2 = await fetchFindingsForFile(apiKey, filePath, cloudUrl);
3549
+ const findings = result2.findings.filter((f) => f.filePath === filePath);
2805
3550
  return {
2806
3551
  content: [
2807
3552
  {
@@ -2995,9 +3740,9 @@ function executeWithMiddleware(middlewares, mCtx, handler) {
2995
3740
  function telemetryMiddleware(logger) {
2996
3741
  return async (mCtx, next) => {
2997
3742
  const start = Date.now();
2998
- const result = await next();
3743
+ const result2 = await next();
2999
3744
  logger(`${mCtx.toolKey}: ${Date.now() - start}ms`);
3000
- return result;
3745
+ return result2;
3001
3746
  };
3002
3747
  }
3003
3748
 
@@ -3838,7 +4583,7 @@ Check that your tsconfig.json includes the component directories.`
3838
4583
  var extractorModulePromise = null;
3839
4584
  async function loadExtractorModule() {
3840
4585
  if (!extractorModulePromise) {
3841
- extractorModulePromise = import("./dist-V7D67NXS.js");
4586
+ extractorModulePromise = import("./dist-BDWAHJ4K.js");
3842
4587
  }
3843
4588
  return extractorModulePromise;
3844
4589
  }
@@ -4122,6 +4867,19 @@ function readPackageName(projectRoot) {
4122
4867
 
4123
4868
  // src/adapters/cloud-catalog.ts
4124
4869
  var DEFAULT_CLOUD_URL2 = "https://app.usefragments.com/api/catalog";
4870
+ function mergeDesignSystemMetadata(catalogDesignSystem, contextDesignSystem) {
4871
+ return {
4872
+ name: catalogDesignSystem?.name ?? contextDesignSystem?.name,
4873
+ packageName: catalogDesignSystem?.packageName ?? contextDesignSystem?.packageName ?? null,
4874
+ importPath: catalogDesignSystem?.importPath ?? contextDesignSystem?.importPath ?? catalogDesignSystem?.packageName ?? contextDesignSystem?.packageName ?? null
4875
+ };
4876
+ }
4877
+ function chooseComponentSource(args) {
4878
+ if (args.contextComponents.length > args.catalogComponents.length) {
4879
+ return args.contextComponents;
4880
+ }
4881
+ return args.catalogComponents;
4882
+ }
4125
4883
  var TOKEN_CATEGORY_ALIASES2 = {
4126
4884
  color: ["color", "colors", "accent", "background", "foreground", "danger", "brand"],
4127
4885
  spacing: ["spacing", "space", "padding", "margin", "gap", "inset"],
@@ -4246,8 +5004,8 @@ function mapComponent(component, designSystem) {
4246
5004
  sourceType: "cloud",
4247
5005
  sourcePath: component.sourcePath,
4248
5006
  sourceRepoFullName: component.sourceRepoFullName ?? void 0,
4249
- packageName: designSystem?.packageName ?? void 0,
4250
- importPath: designSystem?.importPath ?? designSystem?.packageName ?? void 0,
5007
+ packageName: component.packageName ?? designSystem?.packageName ?? void 0,
5008
+ importPath: component.importPath ?? component.packageName ?? designSystem?.importPath ?? designSystem?.packageName ?? void 0,
4251
5009
  publicRef: component.publicRef,
4252
5010
  publicSlug: component.publicSlug ?? null,
4253
5011
  isCanonical: component.isCanonical ?? false,
@@ -4314,21 +5072,32 @@ var CloudCatalogAdapter = class {
4314
5072
  );
4315
5073
  }
4316
5074
  const raw = await response.json();
5075
+ const validateFixRaw = validateFixResponse && validateFixResponse.ok ? await validateFixResponse.json() : void 0;
5076
+ const designSystem = mergeDesignSystemMetadata(
5077
+ raw.designSystem,
5078
+ validateFixRaw?.content?.designSystem
5079
+ );
5080
+ const sourceComponents = chooseComponentSource({
5081
+ catalogComponents: raw.components ?? [],
5082
+ contextComponents: validateFixRaw?.content?.components ?? []
5083
+ });
4317
5084
  const components = Object.fromEntries(
4318
- (raw.components ?? []).map((component) => [
5085
+ sourceComponents.map((component) => [
4319
5086
  component.componentKey,
4320
- mapComponent(component, raw.designSystem)
5087
+ mapComponent(component, designSystem)
4321
5088
  ])
4322
5089
  );
4323
5090
  const tokens = raw.tokens?.flat ? groupTokens(raw.tokens.flat) : void 0;
4324
- const packageName = raw.designSystem?.packageName ?? void 0;
4325
- const importPath = raw.designSystem?.importPath ?? raw.designSystem?.packageName ?? void 0;
4326
- const packageMap = packageName ? Object.fromEntries(
5091
+ const packageName = designSystem?.packageName ?? void 0;
5092
+ const importPath = designSystem?.importPath ?? designSystem?.packageName ?? void 0;
5093
+ const packageMap = Object.fromEntries(
4327
5094
  Object.values(components).map((component) => [
4328
5095
  component.name,
4329
- packageName
4330
- ])
4331
- ) : {};
5096
+ component.importPath ?? component.packageName ?? packageName
5097
+ ]).filter(
5098
+ (entry) => typeof entry[1] === "string" && entry[1].length > 0
5099
+ )
5100
+ );
4332
5101
  const snapshot = validateSnapshot({
4333
5102
  schemaVersion: 1,
4334
5103
  sourceType: "cloud",
@@ -4338,7 +5107,7 @@ var CloudCatalogAdapter = class {
4338
5107
  tokens
4339
5108
  }),
4340
5109
  metadata: {
4341
- designSystemName: raw.designSystem?.name ?? raw.org.name,
5110
+ designSystemName: designSystem?.name ?? raw.org.name,
4342
5111
  packageName,
4343
5112
  importPath,
4344
5113
  revision: raw.revision,
@@ -4349,9 +5118,7 @@ var CloudCatalogAdapter = class {
4349
5118
  packageMap,
4350
5119
  defaultPackageName: packageName
4351
5120
  });
4352
- const validateFixContext = validateFixResponse && validateFixResponse.ok ? normalizeValidateFixContext(
4353
- await validateFixResponse.json()
4354
- ) : void 0;
5121
+ const validateFixContext = validateFixRaw ? normalizeValidateFixContext(validateFixRaw) : void 0;
4355
5122
  const hydratedComponents = Object.fromEntries(
4356
5123
  Object.entries(snapshot.components).map(([componentId, component]) => [
4357
5124
  componentId,
@@ -4690,7 +5457,8 @@ function createMcpServer(config) {
4690
5457
  },
4691
5458
  {
4692
5459
  capabilities: {
4693
- tools: { listChanged: true }
5460
+ tools: { listChanged: true },
5461
+ logging: {}
4694
5462
  }
4695
5463
  }
4696
5464
  );
@@ -4728,9 +5496,9 @@ function createMcpServer(config) {
4728
5496
  if (resolveProjectRootPromise) return resolveProjectRootPromise;
4729
5497
  resolveProjectRootPromise = (async () => {
4730
5498
  try {
4731
- const result = await server.listRoots();
4732
- if (result.roots?.length > 0) {
4733
- const rootUri = result.roots[0].uri;
5499
+ const result2 = await server.listRoots();
5500
+ if (result2.roots?.length > 0) {
5501
+ const rootUri = result2.roots[0].uri;
4734
5502
  resolvedRoot = fileURLToPath(rootUri);
4735
5503
  return resolvedRoot;
4736
5504
  }
@@ -4785,12 +5553,19 @@ function createMcpServer(config) {
4785
5553
  data,
4786
5554
  config: mergedConfig,
4787
5555
  indexes: { componentIndex, blockIndex, tokenIndex },
5556
+ mcp: {
5557
+ server,
5558
+ clientCapabilities: server.getClientCapabilities()
5559
+ },
4788
5560
  resolvePackageName: (name2) => {
4789
5561
  if (name2) {
4790
5562
  const pkg = data.packageMap[name2];
4791
5563
  if (pkg) return pkg;
4792
5564
  }
4793
5565
  if (data.defaultPackageName) return data.defaultPackageName;
5566
+ if (data.snapshot.sourceType === "cloud") {
5567
+ return "your-component-library";
5568
+ }
4794
5569
  const root = resolvedRoot ?? config.projectRoot;
4795
5570
  const packageJsonPath = join8(root, "package.json");
4796
5571
  if (existsSync9(packageJsonPath)) {
@@ -4882,4 +5657,4 @@ export {
4882
5657
  startMcpServer,
4883
5658
  createSandboxServer
4884
5659
  };
4885
- //# sourceMappingURL=chunk-6JMX4AMO.js.map
5660
+ //# sourceMappingURL=chunk-YSNIGHNU.js.map