@fragments-sdk/mcp 0.7.0 → 0.8.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/dist/bin.js +3 -3
- package/dist/{chunk-LZW5KUXV.js → chunk-6JMX4AMO.js} +900 -308
- package/dist/chunk-6JMX4AMO.js.map +1 -0
- package/dist/{chunk-7GGI7JJE.js → chunk-VV2PJ75X.js} +2 -2
- package/dist/chunk-YSRGQDEB.js +93 -0
- package/dist/chunk-YSRGQDEB.js.map +1 -0
- package/dist/index.js +3 -3
- package/dist/rules-CKBRD3UL.js +8 -0
- package/dist/server.js +2 -2
- package/package.json +5 -5
- package/dist/chunk-3IHXJEMM.js +0 -47
- package/dist/chunk-3IHXJEMM.js.map +0 -1
- package/dist/chunk-LZW5KUXV.js.map +0 -1
- package/dist/rules-W47BYPZV.js +0 -8
- /package/dist/{chunk-7GGI7JJE.js.map → chunk-VV2PJ75X.js.map} +0 -0
- /package/dist/{rules-W47BYPZV.js.map → rules-CKBRD3UL.js.map} +0 -0
|
@@ -4,12 +4,13 @@ import {
|
|
|
4
4
|
} from "./chunk-4SVS3AA3.js";
|
|
5
5
|
import {
|
|
6
6
|
componentNames,
|
|
7
|
+
findComponent,
|
|
7
8
|
findComponentByName,
|
|
8
9
|
getGuidanceWhen,
|
|
9
10
|
getGuidanceWhenNot,
|
|
10
11
|
listBlocks,
|
|
11
12
|
listComponents
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-YSRGQDEB.js";
|
|
13
14
|
|
|
14
15
|
// src/server.ts
|
|
15
16
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -19,7 +20,7 @@ import {
|
|
|
19
20
|
ListToolsRequestSchema
|
|
20
21
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
21
22
|
import { existsSync as existsSync9 } from "fs";
|
|
22
|
-
import { readFileSync as
|
|
23
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
23
24
|
import { join as join8 } from "path";
|
|
24
25
|
import { fileURLToPath } from "url";
|
|
25
26
|
|
|
@@ -653,17 +654,20 @@ function buildLocalSearchData(data, indexes) {
|
|
|
653
654
|
}
|
|
654
655
|
async function buildImportStatements(components, resolvePackageName) {
|
|
655
656
|
const grouped = /* @__PURE__ */ new Map();
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
657
|
+
const uniqueComponents = [...new Set(components.filter(Boolean))];
|
|
658
|
+
const resolvedPackages = await Promise.all(
|
|
659
|
+
uniqueComponents.map(async (component) => ({
|
|
660
|
+
component,
|
|
661
|
+
packageName: await resolvePackageName(component)
|
|
662
|
+
}))
|
|
663
|
+
);
|
|
664
|
+
for (const { component, packageName } of resolvedPackages) {
|
|
659
665
|
const existing = grouped.get(packageName);
|
|
660
666
|
if (!existing) {
|
|
661
667
|
grouped.set(packageName, [component]);
|
|
662
668
|
continue;
|
|
663
669
|
}
|
|
664
|
-
|
|
665
|
-
existing.push(component);
|
|
666
|
-
}
|
|
670
|
+
existing.push(component);
|
|
667
671
|
}
|
|
668
672
|
return Array.from(grouped.entries()).map(
|
|
669
673
|
([packageName, componentNames2]) => `import { ${componentNames2.join(", ")} } from '${packageName}';`
|
|
@@ -687,6 +691,56 @@ function limitTokensPerCategory(categories, limit) {
|
|
|
687
691
|
return { categories: limited, total };
|
|
688
692
|
}
|
|
689
693
|
|
|
694
|
+
// src/search-helpers.ts
|
|
695
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
696
|
+
"a",
|
|
697
|
+
"an",
|
|
698
|
+
"and",
|
|
699
|
+
"bar",
|
|
700
|
+
"build",
|
|
701
|
+
"button",
|
|
702
|
+
"for",
|
|
703
|
+
"form",
|
|
704
|
+
"i",
|
|
705
|
+
"login",
|
|
706
|
+
"me",
|
|
707
|
+
"need",
|
|
708
|
+
"of",
|
|
709
|
+
"or",
|
|
710
|
+
"the",
|
|
711
|
+
"to",
|
|
712
|
+
"use",
|
|
713
|
+
"with"
|
|
714
|
+
]);
|
|
715
|
+
function normalizeTerms(value) {
|
|
716
|
+
return value.toLowerCase().split(/[^a-z0-9]+/g).filter((term) => term.length > 1 && !STOP_WORDS.has(term));
|
|
717
|
+
}
|
|
718
|
+
function hasDirectQueryOverlap(query, component) {
|
|
719
|
+
const queryTerms = normalizeTerms(query);
|
|
720
|
+
if (queryTerms.length === 0) return true;
|
|
721
|
+
const haystack = new Set(
|
|
722
|
+
normalizeTerms(
|
|
723
|
+
[
|
|
724
|
+
component.name,
|
|
725
|
+
component.description,
|
|
726
|
+
component.category,
|
|
727
|
+
component.tags.join(" "),
|
|
728
|
+
getGuidanceWhen(component).join(" "),
|
|
729
|
+
getGuidanceWhenNot(component).join(" "),
|
|
730
|
+
component.propsSummary.join(" ")
|
|
731
|
+
].join(" ")
|
|
732
|
+
)
|
|
733
|
+
);
|
|
734
|
+
return queryTerms.some((term) => haystack.has(term));
|
|
735
|
+
}
|
|
736
|
+
function getRankingBonus(component) {
|
|
737
|
+
let bonus = 0;
|
|
738
|
+
if (component.isCanonical) bonus += 30;
|
|
739
|
+
if (component.tier === "core") bonus += 20;
|
|
740
|
+
if (component.status === "stable") bonus += 5;
|
|
741
|
+
return bonus;
|
|
742
|
+
}
|
|
743
|
+
|
|
690
744
|
// src/tools/discover.ts
|
|
691
745
|
function renderContextMarkdown(args) {
|
|
692
746
|
const lines = ["# Design System Context", ""];
|
|
@@ -769,11 +823,13 @@ var discoverHandler = async (args, ctx) => {
|
|
|
769
823
|
const compact = args?.compact ?? false;
|
|
770
824
|
const includeCode = args?.includeCode ?? false;
|
|
771
825
|
const includeRelations = args?.includeRelations ?? false;
|
|
826
|
+
const depth = args?.depth ?? "quick";
|
|
772
827
|
const suggestLimit = typeof args?.limit === "number" ? Math.min(Math.max(args.limit, 1), 25) : 10;
|
|
773
828
|
const listLimit = typeof args?.limit === "number" ? Math.min(Math.max(args.limit, 1), 25) : void 0;
|
|
774
829
|
const verbosity = args?.verbosity ?? (compact ? "compact" : "standard");
|
|
775
|
-
|
|
776
|
-
|
|
830
|
+
const allSnapshotComponents = listComponents(data.snapshot);
|
|
831
|
+
if (!useCase && !componentForAlts && (args?.format || includeCode || includeRelations)) {
|
|
832
|
+
let components2 = allSnapshotComponents;
|
|
777
833
|
const allBlocks = listBlocks(data.snapshot);
|
|
778
834
|
if (category) {
|
|
779
835
|
components2 = components2.filter(
|
|
@@ -781,12 +837,16 @@ var discoverHandler = async (args, ctx) => {
|
|
|
781
837
|
);
|
|
782
838
|
}
|
|
783
839
|
if (search2) {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
840
|
+
const scored = keywordScoreComponents(
|
|
841
|
+
search2,
|
|
842
|
+
components2,
|
|
843
|
+
ctx.indexes.componentIndex ?? void 0
|
|
844
|
+
);
|
|
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());
|
|
847
|
+
components2 = components2.filter((component) => sortedNames.includes(component.name.toLowerCase())).sort(
|
|
848
|
+
(a, b) => sortedNames.indexOf(a.name.toLowerCase()) - sortedNames.indexOf(b.name.toLowerCase())
|
|
849
|
+
);
|
|
790
850
|
}
|
|
791
851
|
if (status) {
|
|
792
852
|
components2 = components2.filter((component) => component.status === status);
|
|
@@ -838,6 +898,14 @@ var discoverHandler = async (args, ctx) => {
|
|
|
838
898
|
"component",
|
|
839
899
|
ctx.config.searchApiKey
|
|
840
900
|
);
|
|
901
|
+
const filteredSearchResults = searchResults.filter((result) => {
|
|
902
|
+
const component = componentsByName.get(result.name.toLowerCase());
|
|
903
|
+
if (!component) return false;
|
|
904
|
+
if (category && !categoryMatches(component.category, category)) return false;
|
|
905
|
+
if (status && component.status !== status) return false;
|
|
906
|
+
result.score += getRankingBonus(component);
|
|
907
|
+
return true;
|
|
908
|
+
});
|
|
841
909
|
const blockMatches = keywordScoreBlocks(
|
|
842
910
|
fullQuery,
|
|
843
911
|
allBlocks,
|
|
@@ -848,10 +916,10 @@ var discoverHandler = async (args, ctx) => {
|
|
|
848
916
|
(match) => allBlocks.find((block) => block.name.toLowerCase() === match.name.toLowerCase())
|
|
849
917
|
).filter(Boolean);
|
|
850
918
|
const blockFreq = buildBlockComponentFrequency(matchedBlocks);
|
|
851
|
-
boostByBlockFrequency(
|
|
919
|
+
boostByBlockFrequency(filteredSearchResults, blockFreq);
|
|
852
920
|
}
|
|
853
|
-
const maxScore =
|
|
854
|
-
const scored =
|
|
921
|
+
const maxScore = filteredSearchResults.length > 0 ? filteredSearchResults[0].score : 0;
|
|
922
|
+
const scored = filteredSearchResults.map((result) => {
|
|
855
923
|
const component = componentsByName.get(result.name.toLowerCase());
|
|
856
924
|
if (!component) return null;
|
|
857
925
|
return {
|
|
@@ -864,6 +932,11 @@ var discoverHandler = async (args, ctx) => {
|
|
|
864
932
|
when: getGuidanceWhen(component).slice(0, 3),
|
|
865
933
|
whenNot: getGuidanceWhenNot(component).slice(0, 2)
|
|
866
934
|
},
|
|
935
|
+
publicRef: component.publicRef,
|
|
936
|
+
componentKey: component.id,
|
|
937
|
+
tier: component.tier,
|
|
938
|
+
isCanonical: component.isCanonical ?? false,
|
|
939
|
+
sourcePath: component.sourcePath,
|
|
867
940
|
exampleCount: component.examples.length,
|
|
868
941
|
status: component.status
|
|
869
942
|
};
|
|
@@ -902,7 +975,10 @@ var discoverHandler = async (args, ctx) => {
|
|
|
902
975
|
const isStyleQuery = styleKeywords.some(
|
|
903
976
|
(keyword) => useCaseLower.includes(keyword)
|
|
904
977
|
);
|
|
905
|
-
const noMatch = suggestions.length === 0
|
|
978
|
+
const noMatch = suggestions.length === 0 || !suggestions.some((item) => {
|
|
979
|
+
const component = componentsByName.get(item.component.toLowerCase());
|
|
980
|
+
return component ? hasDirectQueryOverlap(useCase, component) : false;
|
|
981
|
+
});
|
|
906
982
|
const belowThreshold = !noMatch && maxScore > 1 && !meetsMinimumThreshold(maxScore);
|
|
907
983
|
const weakMatch = !noMatch && (belowThreshold || suggestions.every((item) => item.confidence === "low"));
|
|
908
984
|
let recommendation;
|
|
@@ -922,6 +998,80 @@ var discoverHandler = async (args, ctx) => {
|
|
|
922
998
|
(match) => allBlocks.find((block) => block.name.toLowerCase() === match.name.toLowerCase())
|
|
923
999
|
).filter(Boolean).slice(0, 3).map((block) => block.name);
|
|
924
1000
|
const blockHint = blockNames.length > 0 ? `Related blocks: ${blockNames.join(", ")}. Use ${ctx.toolNames.blocks}(search: "${useCase}") for ready-to-use patterns.` : void 0;
|
|
1001
|
+
let fullBlocks;
|
|
1002
|
+
let fullTokens;
|
|
1003
|
+
let fullImports;
|
|
1004
|
+
if (depth === "full" && !noMatch) {
|
|
1005
|
+
const tokenData = ctx.data.tokens;
|
|
1006
|
+
const [blockSearchResults, tokenSearchResults] = await Promise.all([
|
|
1007
|
+
hybridSearch(fullQuery, localData, 5, "block", ctx.config.searchApiKey),
|
|
1008
|
+
tokenData ? hybridSearch(fullQuery, localData, 10, "token", ctx.config.searchApiKey) : Promise.resolve([])
|
|
1009
|
+
]);
|
|
1010
|
+
const topBlockScore = blockSearchResults.length > 0 ? blockSearchResults[0].score : 0;
|
|
1011
|
+
const relevantBlockResults = blockSearchResults.filter(
|
|
1012
|
+
(result) => result.score >= topBlockScore * 0.3
|
|
1013
|
+
);
|
|
1014
|
+
if (relevantBlockResults.length > 0) {
|
|
1015
|
+
fullBlocks = (await Promise.all(
|
|
1016
|
+
relevantBlockResults.slice(0, 5).map(async (result) => {
|
|
1017
|
+
const block = allBlocks.find(
|
|
1018
|
+
(entry) => entry.name.toLowerCase() === result.name.toLowerCase()
|
|
1019
|
+
);
|
|
1020
|
+
if (!block) return null;
|
|
1021
|
+
const imports = await buildImportStatements(
|
|
1022
|
+
block.components,
|
|
1023
|
+
async (componentName) => ctx.resolvePackageName(componentName)
|
|
1024
|
+
);
|
|
1025
|
+
const codeLines = block.code.split("\n");
|
|
1026
|
+
const code = codeLines.length > 30 ? `${codeLines.slice(0, 20).join("\n")}
|
|
1027
|
+
// ... truncated (${codeLines.length} lines total)` : block.code;
|
|
1028
|
+
return {
|
|
1029
|
+
name: block.name,
|
|
1030
|
+
description: block.description,
|
|
1031
|
+
components: block.components,
|
|
1032
|
+
code,
|
|
1033
|
+
imports
|
|
1034
|
+
};
|
|
1035
|
+
})
|
|
1036
|
+
)).filter(Boolean);
|
|
1037
|
+
}
|
|
1038
|
+
if (tokenSearchResults.length > 0 && tokenData) {
|
|
1039
|
+
fullTokens = {};
|
|
1040
|
+
const tokensByName = /* @__PURE__ */ new Map();
|
|
1041
|
+
for (const [cat, tokens] of Object.entries(tokenData.categories)) {
|
|
1042
|
+
for (const token of tokens) {
|
|
1043
|
+
tokensByName.set(token.name, cat);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
for (const result of tokenSearchResults) {
|
|
1047
|
+
const cat = tokensByName.get(result.name);
|
|
1048
|
+
if (cat) {
|
|
1049
|
+
if (!fullTokens[cat]) fullTokens[cat] = [];
|
|
1050
|
+
fullTokens[cat].push(result.name);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
if (Object.keys(fullTokens).length === 0) fullTokens = void 0;
|
|
1054
|
+
}
|
|
1055
|
+
if (!fullTokens && tokenData) {
|
|
1056
|
+
const categories = extractTokenCategories(fullQuery);
|
|
1057
|
+
fullTokens = {};
|
|
1058
|
+
for (const cat of categories) {
|
|
1059
|
+
const tokens = tokenData.categories[cat];
|
|
1060
|
+
if (tokens && tokens.length > 0) {
|
|
1061
|
+
fullTokens[cat] = tokens.slice(0, 5).map((token) => token.name);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
if (Object.keys(fullTokens).length === 0) fullTokens = void 0;
|
|
1065
|
+
}
|
|
1066
|
+
if (suggestions.length > 0) {
|
|
1067
|
+
fullImports = {};
|
|
1068
|
+
for (const item of suggestions) {
|
|
1069
|
+
if (!item) continue;
|
|
1070
|
+
const pkgName = ctx.resolvePackageName(item.component);
|
|
1071
|
+
fullImports[item.component] = `import { ${item.component} } from '${pkgName}';`;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
925
1075
|
const suggestResponse = verbosity === "compact" ? {
|
|
926
1076
|
useCase,
|
|
927
1077
|
suggestions: suggestions.map((item) => ({
|
|
@@ -933,14 +1083,19 @@ var discoverHandler = async (args, ctx) => {
|
|
|
933
1083
|
} : {
|
|
934
1084
|
useCase,
|
|
935
1085
|
context: context || void 0,
|
|
936
|
-
suggestions
|
|
1086
|
+
suggestions: depth === "full" ? suggestions.map((item) => ({
|
|
1087
|
+
...item,
|
|
1088
|
+
import: fullImports?.[item.component]
|
|
1089
|
+
})) : suggestions,
|
|
937
1090
|
noMatch,
|
|
938
1091
|
weakMatch,
|
|
939
1092
|
recommendation,
|
|
940
1093
|
compositionHint,
|
|
941
1094
|
...tokenHint && { tokenHint },
|
|
942
1095
|
...blockHint && { blockHint },
|
|
943
|
-
nextStep
|
|
1096
|
+
nextStep,
|
|
1097
|
+
...fullBlocks && fullBlocks.length > 0 && { blocks: fullBlocks },
|
|
1098
|
+
...fullTokens && { tokens: fullTokens }
|
|
944
1099
|
};
|
|
945
1100
|
return {
|
|
946
1101
|
content: [
|
|
@@ -952,33 +1107,39 @@ var discoverHandler = async (args, ctx) => {
|
|
|
952
1107
|
};
|
|
953
1108
|
}
|
|
954
1109
|
if (componentForAlts) {
|
|
955
|
-
const component =
|
|
1110
|
+
const component = findComponent(data.snapshot, componentForAlts);
|
|
956
1111
|
if (!component) {
|
|
957
1112
|
const closest = findClosestMatch(
|
|
958
1113
|
componentForAlts,
|
|
959
1114
|
componentNames(data.snapshot)
|
|
960
1115
|
);
|
|
961
1116
|
const suggestion = closest ? ` Did you mean "${closest}"?` : "";
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1117
|
+
return {
|
|
1118
|
+
content: [
|
|
1119
|
+
{
|
|
1120
|
+
type: "text",
|
|
1121
|
+
text: JSON.stringify({
|
|
1122
|
+
error: `Component "${componentForAlts}" not found.${suggestion} Use ${ctx.toolNames.discover} to see available components.`
|
|
1123
|
+
})
|
|
1124
|
+
}
|
|
1125
|
+
],
|
|
1126
|
+
isError: true
|
|
1127
|
+
};
|
|
965
1128
|
}
|
|
966
1129
|
const relations = component.relations;
|
|
967
|
-
const referencedBy =
|
|
968
|
-
|
|
969
|
-
(
|
|
970
|
-
)
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
const sameCategory = listComponents(data.snapshot).filter(
|
|
981
|
-
(entry) => entry.category === component.category && entry.name.toLowerCase() !== componentForAlts.toLowerCase()
|
|
1130
|
+
const referencedBy = allSnapshotComponents.map((entry) => {
|
|
1131
|
+
const relation = entry.relations.find(
|
|
1132
|
+
(candidate) => candidate.componentName.toLowerCase() === component.name.toLowerCase()
|
|
1133
|
+
);
|
|
1134
|
+
if (!relation) return null;
|
|
1135
|
+
return {
|
|
1136
|
+
component: entry.name,
|
|
1137
|
+
relationship: relation.relationship,
|
|
1138
|
+
note: relation.note
|
|
1139
|
+
};
|
|
1140
|
+
}).filter(Boolean);
|
|
1141
|
+
const sameCategory = allSnapshotComponents.filter(
|
|
1142
|
+
(entry) => entry.category === component.category && entry.name.toLowerCase() !== component.name.toLowerCase()
|
|
982
1143
|
).map((entry) => ({
|
|
983
1144
|
component: entry.name,
|
|
984
1145
|
description: entry.description
|
|
@@ -1001,25 +1162,44 @@ var discoverHandler = async (args, ctx) => {
|
|
|
1001
1162
|
]
|
|
1002
1163
|
};
|
|
1003
1164
|
}
|
|
1004
|
-
|
|
1165
|
+
let filteredComponents = allSnapshotComponents.filter((component) => {
|
|
1005
1166
|
if (category && !categoryMatches(component.category, category)) return false;
|
|
1006
1167
|
if (status && (component.status ?? "stable") !== status) return false;
|
|
1007
|
-
if (search2) {
|
|
1008
|
-
const nameMatch = component.name.toLowerCase().includes(search2);
|
|
1009
|
-
const descMatch = component.description.toLowerCase().includes(search2);
|
|
1010
|
-
const tagMatch = component.tags.some(
|
|
1011
|
-
(tag) => tag.toLowerCase().includes(search2)
|
|
1012
|
-
);
|
|
1013
|
-
if (!nameMatch && !descMatch && !tagMatch) return false;
|
|
1014
|
-
}
|
|
1015
1168
|
return true;
|
|
1016
1169
|
});
|
|
1170
|
+
if (search2) {
|
|
1171
|
+
const scored = keywordScoreComponents(
|
|
1172
|
+
search2,
|
|
1173
|
+
filteredComponents,
|
|
1174
|
+
ctx.indexes.componentIndex ?? void 0
|
|
1175
|
+
).map((result) => {
|
|
1176
|
+
const component = filteredComponents.find(
|
|
1177
|
+
(entry) => entry.name.toLowerCase() === result.name.toLowerCase()
|
|
1178
|
+
);
|
|
1179
|
+
if (!component) return null;
|
|
1180
|
+
return {
|
|
1181
|
+
component,
|
|
1182
|
+
score: result.score + getRankingBonus(component)
|
|
1183
|
+
};
|
|
1184
|
+
}).filter(Boolean);
|
|
1185
|
+
filteredComponents = scored.sort((a, b) => b.score - a.score).map((entry) => entry.component);
|
|
1186
|
+
} else {
|
|
1187
|
+
filteredComponents = filteredComponents.sort((a, b) => {
|
|
1188
|
+
const bonusDiff = getRankingBonus(b) - getRankingBonus(a);
|
|
1189
|
+
if (bonusDiff !== 0) return bonusDiff;
|
|
1190
|
+
return a.name.localeCompare(b.name);
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1017
1193
|
const limitedComponents = listLimit === void 0 ? filteredComponents : filteredComponents.slice(0, listLimit);
|
|
1018
1194
|
const components = limitedComponents.map((component) => {
|
|
1019
1195
|
if (verbosity === "compact") {
|
|
1020
1196
|
return {
|
|
1021
1197
|
name: component.name,
|
|
1022
1198
|
category: component.category,
|
|
1199
|
+
publicRef: component.publicRef,
|
|
1200
|
+
componentKey: component.id,
|
|
1201
|
+
tier: component.tier,
|
|
1202
|
+
isCanonical: component.isCanonical ?? false,
|
|
1023
1203
|
...component.propsSummary.length > 0 && {
|
|
1024
1204
|
propsSummary: component.propsSummary
|
|
1025
1205
|
}
|
|
@@ -1030,6 +1210,11 @@ var discoverHandler = async (args, ctx) => {
|
|
|
1030
1210
|
category: component.category,
|
|
1031
1211
|
description: component.description,
|
|
1032
1212
|
status: component.status ?? "stable",
|
|
1213
|
+
publicRef: component.publicRef,
|
|
1214
|
+
componentKey: component.id,
|
|
1215
|
+
tier: component.tier,
|
|
1216
|
+
isCanonical: component.isCanonical ?? false,
|
|
1217
|
+
sourcePath: component.sourcePath,
|
|
1033
1218
|
exampleCount: component.examples.length,
|
|
1034
1219
|
tags: component.tags,
|
|
1035
1220
|
...(includeCode || verbosity === "full") && component.examples[0]?.code ? {
|
|
@@ -1054,7 +1239,8 @@ var discoverHandler = async (args, ctx) => {
|
|
|
1054
1239
|
};
|
|
1055
1240
|
|
|
1056
1241
|
// src/tools/inspect.ts
|
|
1057
|
-
import {
|
|
1242
|
+
import { promises as fs } from "fs";
|
|
1243
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1058
1244
|
import { join as join2 } from "path";
|
|
1059
1245
|
|
|
1060
1246
|
// src/utils.ts
|
|
@@ -1090,13 +1276,13 @@ function projectFields(obj, fields) {
|
|
|
1090
1276
|
}
|
|
1091
1277
|
|
|
1092
1278
|
// src/tools/inspect.ts
|
|
1093
|
-
function getSourceCode(component, projectRoot) {
|
|
1279
|
+
async function getSourceCode(component, projectRoot) {
|
|
1094
1280
|
const sourcePath = component.sourcePath;
|
|
1095
1281
|
if (!sourcePath) return void 0;
|
|
1096
1282
|
const fullPath = join2(projectRoot, sourcePath);
|
|
1097
1283
|
if (!existsSync2(fullPath)) return { path: sourcePath, code: null };
|
|
1098
1284
|
try {
|
|
1099
|
-
const code =
|
|
1285
|
+
const code = await fs.readFile(fullPath, "utf-8");
|
|
1100
1286
|
return { path: sourcePath, code };
|
|
1101
1287
|
} catch {
|
|
1102
1288
|
return { path: sourcePath, code: null };
|
|
@@ -1110,18 +1296,34 @@ var inspectHandler = async (args, ctx) => {
|
|
|
1110
1296
|
const maxLines = args?.maxLines;
|
|
1111
1297
|
const verbosity = args?.verbosity ?? "standard";
|
|
1112
1298
|
if (!componentName) {
|
|
1113
|
-
|
|
1299
|
+
return {
|
|
1300
|
+
content: [
|
|
1301
|
+
{
|
|
1302
|
+
type: "text",
|
|
1303
|
+
text: JSON.stringify({ error: "component is required" })
|
|
1304
|
+
}
|
|
1305
|
+
],
|
|
1306
|
+
isError: true
|
|
1307
|
+
};
|
|
1114
1308
|
}
|
|
1115
|
-
const component =
|
|
1309
|
+
const component = findComponent(ctx.data.snapshot, componentName);
|
|
1116
1310
|
if (!component) {
|
|
1117
1311
|
const closest = findClosestMatch(
|
|
1118
1312
|
componentName,
|
|
1119
1313
|
componentNames(ctx.data.snapshot)
|
|
1120
1314
|
);
|
|
1121
1315
|
const suggestion = closest ? ` Did you mean "${closest}"? Use ${ctx.toolNames.inspect}("${closest}") to inspect it.` : "";
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1316
|
+
return {
|
|
1317
|
+
content: [
|
|
1318
|
+
{
|
|
1319
|
+
type: "text",
|
|
1320
|
+
text: JSON.stringify({
|
|
1321
|
+
error: `Component "${componentName}" not found.${suggestion} Use ${ctx.toolNames.discover} to see available components.`
|
|
1322
|
+
})
|
|
1323
|
+
}
|
|
1324
|
+
],
|
|
1325
|
+
isError: true
|
|
1326
|
+
};
|
|
1125
1327
|
}
|
|
1126
1328
|
const pkgName = ctx.resolvePackageName(component.name);
|
|
1127
1329
|
let examples = component.examples;
|
|
@@ -1141,25 +1343,41 @@ var inspectHandler = async (args, ctx) => {
|
|
|
1141
1343
|
if (filtered.length > 0) {
|
|
1142
1344
|
examples = filtered;
|
|
1143
1345
|
} else {
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1346
|
+
return {
|
|
1347
|
+
content: [
|
|
1348
|
+
{
|
|
1349
|
+
type: "text",
|
|
1350
|
+
text: JSON.stringify({
|
|
1351
|
+
error: `Example "${exampleName}" not found for ${componentName}. Available: ${component.examples.map((example) => example.name).join(", ")}`
|
|
1352
|
+
})
|
|
1353
|
+
}
|
|
1354
|
+
],
|
|
1355
|
+
isError: true
|
|
1356
|
+
};
|
|
1147
1357
|
}
|
|
1148
1358
|
}
|
|
1149
1359
|
if (maxExamples && maxExamples > 0) {
|
|
1150
1360
|
examples = examples.slice(0, maxExamples);
|
|
1151
1361
|
}
|
|
1152
1362
|
const truncateCode = (code) => {
|
|
1153
|
-
if (!maxLines || maxLines <= 0)
|
|
1363
|
+
if (!maxLines || maxLines <= 0) {
|
|
1364
|
+
return { code, truncated: false, remainingLines: 0 };
|
|
1365
|
+
}
|
|
1154
1366
|
const lines = code.split("\n");
|
|
1155
|
-
if (lines.length <= maxLines)
|
|
1156
|
-
|
|
1157
|
-
|
|
1367
|
+
if (lines.length <= maxLines) {
|
|
1368
|
+
return { code, truncated: false, remainingLines: 0 };
|
|
1369
|
+
}
|
|
1370
|
+
return {
|
|
1371
|
+
code: lines.slice(0, maxLines).join("\n"),
|
|
1372
|
+
truncated: true,
|
|
1373
|
+
remainingLines: lines.length - maxLines
|
|
1374
|
+
};
|
|
1158
1375
|
};
|
|
1159
1376
|
const renderedExamples = examples.map((example) => ({
|
|
1377
|
+
...example.code ? truncateCode(example.code) : { truncated: false, remainingLines: 0 },
|
|
1160
1378
|
variant: example.name,
|
|
1161
1379
|
description: example.description,
|
|
1162
|
-
code: example.code ? truncateCode(example.code) : `<${component.name} />`,
|
|
1380
|
+
code: example.code ? truncateCode(example.code).code : `<${component.name} />`,
|
|
1163
1381
|
...example.code ? {} : {
|
|
1164
1382
|
note: "No code example provided. Refer to props for customization."
|
|
1165
1383
|
}
|
|
@@ -1189,13 +1407,13 @@ var inspectHandler = async (args, ctx) => {
|
|
|
1189
1407
|
status: component.status,
|
|
1190
1408
|
publicRef: component.publicRef,
|
|
1191
1409
|
publicSlug: component.publicSlug,
|
|
1410
|
+
isCanonical: component.isCanonical ?? false,
|
|
1192
1411
|
tier: component.tier
|
|
1193
1412
|
},
|
|
1194
|
-
props:
|
|
1413
|
+
props: propsReference,
|
|
1195
1414
|
examples: {
|
|
1196
1415
|
import: `import { ${component.name} } from '${pkgName}';`,
|
|
1197
|
-
code: renderedExamples
|
|
1198
|
-
propsReference
|
|
1416
|
+
code: renderedExamples
|
|
1199
1417
|
},
|
|
1200
1418
|
relations: component.relations,
|
|
1201
1419
|
compoundChildren: component.compoundChildren,
|
|
@@ -1215,7 +1433,7 @@ var inspectHandler = async (args, ctx) => {
|
|
|
1215
1433
|
})) ?? []
|
|
1216
1434
|
},
|
|
1217
1435
|
metadata: component.metadata,
|
|
1218
|
-
source: getSourceCode(component, ctx.config.projectRoot)
|
|
1436
|
+
source: await getSourceCode(component, ctx.config.projectRoot)
|
|
1219
1437
|
};
|
|
1220
1438
|
const aliasMap = { usage: "guidance" };
|
|
1221
1439
|
const resolvedFields = fields?.map((field) => {
|
|
@@ -1395,6 +1613,44 @@ function resolveCategoryKeys(categories, requestedCategory) {
|
|
|
1395
1613
|
}
|
|
1396
1614
|
return keys.filter((key) => (normalizeCategoryValue(key) ?? "").includes(normalized));
|
|
1397
1615
|
}
|
|
1616
|
+
function canonicalizeCategory(category, tokens) {
|
|
1617
|
+
const normalizedCategory = normalizeCategoryValue(category);
|
|
1618
|
+
const candidates = [
|
|
1619
|
+
normalizedCategory,
|
|
1620
|
+
...tokens.flatMap((token) => [
|
|
1621
|
+
normalizeCategoryValue(token.category),
|
|
1622
|
+
normalizeCategoryValue(token.path?.[0]),
|
|
1623
|
+
normalizeCategoryValue(token.name.split(/[.:/-]/)[0])
|
|
1624
|
+
])
|
|
1625
|
+
].filter(Boolean);
|
|
1626
|
+
for (const candidate of candidates) {
|
|
1627
|
+
for (const [canonical, aliases] of Object.entries(TOKEN_CATEGORY_ALIASES)) {
|
|
1628
|
+
if (candidate === canonical || aliases.some(
|
|
1629
|
+
(alias) => candidate === alias || candidate.includes(alias) || alias.includes(candidate)
|
|
1630
|
+
)) {
|
|
1631
|
+
return canonical;
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
return normalizedCategory || "other";
|
|
1636
|
+
}
|
|
1637
|
+
function normalizeTokenCategories(categories) {
|
|
1638
|
+
const normalized = {};
|
|
1639
|
+
for (const [category, tokens] of Object.entries(categories)) {
|
|
1640
|
+
const canonical = canonicalizeCategory(category, tokens);
|
|
1641
|
+
if (!normalized[canonical]) normalized[canonical] = [];
|
|
1642
|
+
normalized[canonical].push(
|
|
1643
|
+
...tokens.map((token) => ({
|
|
1644
|
+
...token,
|
|
1645
|
+
category: canonical
|
|
1646
|
+
}))
|
|
1647
|
+
);
|
|
1648
|
+
}
|
|
1649
|
+
for (const tokens of Object.values(normalized)) {
|
|
1650
|
+
tokens.sort((a, b) => a.name.localeCompare(b.name));
|
|
1651
|
+
}
|
|
1652
|
+
return normalized;
|
|
1653
|
+
}
|
|
1398
1654
|
var tokensHandler = async (args, ctx) => {
|
|
1399
1655
|
const category = args?.category;
|
|
1400
1656
|
const search2 = args?.search?.toLowerCase() ?? void 0;
|
|
@@ -1412,12 +1668,13 @@ var tokensHandler = async (args, ctx) => {
|
|
|
1412
1668
|
}]
|
|
1413
1669
|
};
|
|
1414
1670
|
}
|
|
1671
|
+
const normalizedCategories = normalizeTokenCategories(tokenData.categories);
|
|
1415
1672
|
let filteredCategories = {};
|
|
1416
1673
|
let filteredTotal = 0;
|
|
1417
|
-
const resolvedCategoryKeys = resolveCategoryKeys(
|
|
1674
|
+
const resolvedCategoryKeys = resolveCategoryKeys(normalizedCategories, category);
|
|
1418
1675
|
const friendlyCategories = Object.keys(TOKEN_CATEGORY_ALIASES);
|
|
1419
|
-
const searchMatchesCategory = search2 ? resolveCategoryKeys(
|
|
1420
|
-
for (const [cat, tokens] of Object.entries(
|
|
1676
|
+
const searchMatchesCategory = search2 ? resolveCategoryKeys(normalizedCategories, search2) : [];
|
|
1677
|
+
for (const [cat, tokens] of Object.entries(normalizedCategories)) {
|
|
1421
1678
|
if (category && !resolvedCategoryKeys.includes(cat)) continue;
|
|
1422
1679
|
let filtered = tokens;
|
|
1423
1680
|
if (search2) {
|
|
@@ -1425,7 +1682,7 @@ var tokensHandler = async (args, ctx) => {
|
|
|
1425
1682
|
filtered = tokens;
|
|
1426
1683
|
} else {
|
|
1427
1684
|
filtered = tokens.filter(
|
|
1428
|
-
(t) => t.name.toLowerCase().includes(search2) || t.description && t.description.toLowerCase().includes(search2) || (normalizeCategoryValue(cat) ?? "").includes(search2)
|
|
1685
|
+
(t) => t.name.toLowerCase().includes(search2) || t.description && t.description.toLowerCase().includes(search2) || (normalizeCategoryValue(cat) ?? "").includes(search2) || t.value && t.value.toLowerCase().includes(search2) || t.path && t.path.join(" ").toLowerCase().includes(search2)
|
|
1429
1686
|
);
|
|
1430
1687
|
}
|
|
1431
1688
|
}
|
|
@@ -1443,7 +1700,7 @@ var tokensHandler = async (args, ctx) => {
|
|
|
1443
1700
|
if (filteredTotal === 0) {
|
|
1444
1701
|
if (category && search2) {
|
|
1445
1702
|
const categoryTotal = resolvedCategoryKeys.reduce(
|
|
1446
|
-
(sum, key) => sum + (
|
|
1703
|
+
(sum, key) => sum + (normalizedCategories[key]?.length ?? 0),
|
|
1447
1704
|
0
|
|
1448
1705
|
);
|
|
1449
1706
|
hint = categoryTotal > 0 ? `No tokens matching "${search2}" in category "${category}" (${categoryTotal} tokens in this category). Try a broader search or remove the search term.` : `Category "${category}" not found. Try categories like: ${friendlyCategories.join(", ")}.`;
|
|
@@ -1465,7 +1722,7 @@ var tokensHandler = async (args, ctx) => {
|
|
|
1465
1722
|
categories: filteredCategories,
|
|
1466
1723
|
...hint && { hint },
|
|
1467
1724
|
...!category && !search2 && {
|
|
1468
|
-
availableCategories: Object.entries(
|
|
1725
|
+
availableCategories: Object.entries(normalizedCategories).map(([cat, tokens]) => ({
|
|
1469
1726
|
category: cat,
|
|
1470
1727
|
count: tokens.length
|
|
1471
1728
|
}))
|
|
@@ -1475,177 +1732,6 @@ var tokensHandler = async (args, ctx) => {
|
|
|
1475
1732
|
};
|
|
1476
1733
|
};
|
|
1477
1734
|
|
|
1478
|
-
// src/tools/implement.ts
|
|
1479
|
-
var implementHandler = async (args, ctx) => {
|
|
1480
|
-
const useCase = args?.useCase;
|
|
1481
|
-
if (!useCase) {
|
|
1482
|
-
throw new Error("useCase is required");
|
|
1483
|
-
}
|
|
1484
|
-
const verbosity = args?.verbosity ?? "standard";
|
|
1485
|
-
const snapshotComponents = listComponents(ctx.data.snapshot);
|
|
1486
|
-
const componentsByName = new Map(
|
|
1487
|
-
snapshotComponents.map((component) => [
|
|
1488
|
-
component.name.toLowerCase(),
|
|
1489
|
-
component
|
|
1490
|
-
])
|
|
1491
|
-
);
|
|
1492
|
-
const { allBlocks, localData } = buildLocalSearchData(
|
|
1493
|
-
{
|
|
1494
|
-
components: ctx.data.components,
|
|
1495
|
-
blocks: ctx.data.blocks,
|
|
1496
|
-
tokens: ctx.data.tokens,
|
|
1497
|
-
graph: ctx.data.graph
|
|
1498
|
-
},
|
|
1499
|
-
{
|
|
1500
|
-
componentIndex: ctx.indexes.componentIndex,
|
|
1501
|
-
blockIndex: ctx.indexes.blockIndex,
|
|
1502
|
-
tokenIndex: ctx.indexes.tokenIndex
|
|
1503
|
-
}
|
|
1504
|
-
);
|
|
1505
|
-
const tokenData = ctx.data.tokens;
|
|
1506
|
-
const implLimit = typeof args?.limit === "number" ? Math.min(Math.max(args.limit, 1), 15) : 5;
|
|
1507
|
-
const [componentResults, blockResults, tokenResults] = await Promise.all([
|
|
1508
|
-
hybridSearch(
|
|
1509
|
-
useCase,
|
|
1510
|
-
localData,
|
|
1511
|
-
implLimit * 3,
|
|
1512
|
-
"component",
|
|
1513
|
-
ctx.config.searchApiKey
|
|
1514
|
-
),
|
|
1515
|
-
hybridSearch(
|
|
1516
|
-
useCase,
|
|
1517
|
-
localData,
|
|
1518
|
-
implLimit,
|
|
1519
|
-
"block",
|
|
1520
|
-
ctx.config.searchApiKey
|
|
1521
|
-
),
|
|
1522
|
-
hybridSearch(
|
|
1523
|
-
useCase,
|
|
1524
|
-
localData,
|
|
1525
|
-
implLimit,
|
|
1526
|
-
"token",
|
|
1527
|
-
ctx.config.searchApiKey
|
|
1528
|
-
)
|
|
1529
|
-
]);
|
|
1530
|
-
const topBlockScore = blockResults.length > 0 ? blockResults[0].score : 0;
|
|
1531
|
-
const filteredBlockResults = blockResults.filter(
|
|
1532
|
-
(result) => result.score >= topBlockScore * 0.3
|
|
1533
|
-
);
|
|
1534
|
-
if (filteredBlockResults.length > 0) {
|
|
1535
|
-
const matchedBlocks = filteredBlockResults.map(
|
|
1536
|
-
(result) => allBlocks.find((block) => block.name.toLowerCase() === result.name.toLowerCase())
|
|
1537
|
-
).filter(Boolean);
|
|
1538
|
-
const blockFreq = buildBlockComponentFrequency(matchedBlocks);
|
|
1539
|
-
boostByBlockFrequency(componentResults, blockFreq);
|
|
1540
|
-
}
|
|
1541
|
-
const topComponentResults = componentResults.slice(0, implLimit);
|
|
1542
|
-
const maxCompScore = topComponentResults.length > 0 ? topComponentResults[0].score : 0;
|
|
1543
|
-
const topMatches = topComponentResults.map((result) => {
|
|
1544
|
-
const component = componentsByName.get(result.name.toLowerCase());
|
|
1545
|
-
return component ? { component, score: result.score } : null;
|
|
1546
|
-
}).filter(Boolean);
|
|
1547
|
-
const components = await Promise.all(
|
|
1548
|
-
topMatches.map(async ({ component, score }) => {
|
|
1549
|
-
const pkgName = ctx.resolvePackageName(component.name);
|
|
1550
|
-
if (verbosity === "compact") {
|
|
1551
|
-
return {
|
|
1552
|
-
name: component.name,
|
|
1553
|
-
description: component.description,
|
|
1554
|
-
confidence: assignConfidence(score, maxCompScore),
|
|
1555
|
-
import: `import { ${component.name} } from '${pkgName}';`
|
|
1556
|
-
};
|
|
1557
|
-
}
|
|
1558
|
-
const exampleLimit = verbosity === "full" ? component.examples.length : 2;
|
|
1559
|
-
const propsLimit = verbosity === "full" ? Object.keys(component.props ?? {}).length : 5;
|
|
1560
|
-
const examples = component.examples.slice(0, exampleLimit).map((example) => ({
|
|
1561
|
-
variant: example.name,
|
|
1562
|
-
code: example.code ?? `<${component.name} />`
|
|
1563
|
-
}));
|
|
1564
|
-
const propsSummary = Object.entries(component.props ?? {}).slice(0, propsLimit).map(
|
|
1565
|
-
([propName, prop]) => `${propName}${prop.required ? " (required)" : ""}: ${prop.type}${prop.values ? ` = ${prop.values.join("|")}` : ""}`
|
|
1566
|
-
);
|
|
1567
|
-
return {
|
|
1568
|
-
name: component.name,
|
|
1569
|
-
category: component.category,
|
|
1570
|
-
description: component.description,
|
|
1571
|
-
confidence: assignConfidence(score, maxCompScore),
|
|
1572
|
-
import: `import { ${component.name} } from '${pkgName}';`,
|
|
1573
|
-
props: propsSummary,
|
|
1574
|
-
examples,
|
|
1575
|
-
guidelines: getGuidanceWhen(component).slice(0, 3),
|
|
1576
|
-
accessibility: component.guidance.accessibility.slice(0, 2)
|
|
1577
|
-
};
|
|
1578
|
-
})
|
|
1579
|
-
);
|
|
1580
|
-
const matchingBlocks = (await Promise.all(
|
|
1581
|
-
filteredBlockResults.slice(0, 5).map(async (result) => {
|
|
1582
|
-
const block = allBlocks.find(
|
|
1583
|
-
(entry) => entry.name.toLowerCase() === result.name.toLowerCase()
|
|
1584
|
-
);
|
|
1585
|
-
if (!block) return null;
|
|
1586
|
-
const imports = await buildImportStatements(
|
|
1587
|
-
block.components,
|
|
1588
|
-
async (componentName) => ctx.resolvePackageName(componentName)
|
|
1589
|
-
);
|
|
1590
|
-
const codeLines = block.code.split("\n");
|
|
1591
|
-
const code = codeLines.length > 30 ? `${codeLines.slice(0, 20).join("\n")}
|
|
1592
|
-
// ... truncated (${codeLines.length} lines total)` : block.code;
|
|
1593
|
-
return {
|
|
1594
|
-
name: block.name,
|
|
1595
|
-
description: block.description,
|
|
1596
|
-
components: block.components,
|
|
1597
|
-
code,
|
|
1598
|
-
import: imports.join("\n"),
|
|
1599
|
-
imports
|
|
1600
|
-
};
|
|
1601
|
-
})
|
|
1602
|
-
)).filter(Boolean);
|
|
1603
|
-
let relevantTokens;
|
|
1604
|
-
if (tokenResults.length > 0 && tokenData) {
|
|
1605
|
-
relevantTokens = {};
|
|
1606
|
-
for (const result of tokenResults) {
|
|
1607
|
-
for (const [category, tokens] of Object.entries(tokenData.categories)) {
|
|
1608
|
-
if (tokens.some((token) => token.name === result.name)) {
|
|
1609
|
-
if (!relevantTokens[category]) relevantTokens[category] = [];
|
|
1610
|
-
relevantTokens[category].push(result.name);
|
|
1611
|
-
break;
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1615
|
-
if (Object.keys(relevantTokens).length === 0) {
|
|
1616
|
-
relevantTokens = void 0;
|
|
1617
|
-
}
|
|
1618
|
-
}
|
|
1619
|
-
if (!relevantTokens && tokenData) {
|
|
1620
|
-
const categories = extractTokenCategories(useCase);
|
|
1621
|
-
relevantTokens = {};
|
|
1622
|
-
for (const category of categories) {
|
|
1623
|
-
const tokens = tokenData.categories[category];
|
|
1624
|
-
if (tokens && tokens.length > 0) {
|
|
1625
|
-
relevantTokens[category] = tokens.slice(0, 5).map((token) => token.name);
|
|
1626
|
-
}
|
|
1627
|
-
}
|
|
1628
|
-
if (Object.keys(relevantTokens).length === 0) {
|
|
1629
|
-
relevantTokens = void 0;
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
|
-
return {
|
|
1633
|
-
content: [
|
|
1634
|
-
{
|
|
1635
|
-
type: "text",
|
|
1636
|
-
text: JSON.stringify({
|
|
1637
|
-
useCase,
|
|
1638
|
-
components,
|
|
1639
|
-
blocks: verbosity !== "compact" && matchingBlocks.length > 0 ? matchingBlocks : void 0,
|
|
1640
|
-
tokens: verbosity !== "compact" ? relevantTokens : void 0,
|
|
1641
|
-
noMatch: components.length === 0,
|
|
1642
|
-
summary: components.length > 0 ? `Found ${components.length} component(s) for "${useCase}". ${matchingBlocks.length > 0 ? `Plus ${matchingBlocks.length} ready-to-use block(s).` : ""}` : `No components match "${useCase}". Try ${ctx.toolNames.discover} with different terms${tokenData ? ` or ${ctx.toolNames.tokens} for tokens` : ""}.`
|
|
1643
|
-
})
|
|
1644
|
-
}
|
|
1645
|
-
]
|
|
1646
|
-
};
|
|
1647
|
-
};
|
|
1648
|
-
|
|
1649
1735
|
// src/service.ts
|
|
1650
1736
|
var DEFAULT_ENDPOINTS = {
|
|
1651
1737
|
render: "/fragments/render",
|
|
@@ -2327,8 +2413,18 @@ var governHandler = async (args, ctx) => {
|
|
|
2327
2413
|
}
|
|
2328
2414
|
const policyOverrides = args?.policy;
|
|
2329
2415
|
const format = args?.format ?? "json";
|
|
2416
|
+
const allowedComponents = Object.values(ctx.data.components).map(
|
|
2417
|
+
(component) => component.name
|
|
2418
|
+
);
|
|
2330
2419
|
const tokenPrefix = ctx.data.tokens?.prefix;
|
|
2331
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
|
+
};
|
|
2332
2428
|
const engineOptions = ctx.data.tokens ? { tokenData: ctx.data.tokens } : void 0;
|
|
2333
2429
|
const input = {
|
|
2334
2430
|
spec,
|
|
@@ -2366,6 +2462,205 @@ var governHandler = async (args, ctx) => {
|
|
|
2366
2462
|
}
|
|
2367
2463
|
};
|
|
2368
2464
|
|
|
2465
|
+
// 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
|
+
function classifyComponent(component) {
|
|
2473
|
+
if (component.status === "deprecated") return "discouraged";
|
|
2474
|
+
if (component.isCanonical && component.tier === "core") return "preferred";
|
|
2475
|
+
return "allowed";
|
|
2476
|
+
}
|
|
2477
|
+
function buildEffectiveComponents(ctx) {
|
|
2478
|
+
const selectionByKey = new Map(
|
|
2479
|
+
(ctx.data.validateFixContext?.components ?? []).map((component) => [
|
|
2480
|
+
component.componentKey,
|
|
2481
|
+
component.selection
|
|
2482
|
+
])
|
|
2483
|
+
);
|
|
2484
|
+
return Object.entries(ctx.data.components).map(([componentKey, component]) => ({
|
|
2485
|
+
component,
|
|
2486
|
+
selection: selectionByKey.get(componentKey) ?? classifyComponent(component)
|
|
2487
|
+
}));
|
|
2488
|
+
}
|
|
2489
|
+
function cloneSpec(spec) {
|
|
2490
|
+
return structuredClone(spec);
|
|
2491
|
+
}
|
|
2492
|
+
function getSelectionNames(effectiveComponents, selection) {
|
|
2493
|
+
const selections = Array.isArray(selection) ? selection : [selection];
|
|
2494
|
+
return Array.from(
|
|
2495
|
+
new Set(
|
|
2496
|
+
effectiveComponents.filter((entry) => selections.includes(entry.selection)).map((entry) => entry.component.name)
|
|
2497
|
+
)
|
|
2498
|
+
);
|
|
2499
|
+
}
|
|
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
|
|
2509
|
+
}
|
|
2510
|
+
};
|
|
2511
|
+
return basePolicy;
|
|
2512
|
+
}
|
|
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);
|
|
2521
|
+
}
|
|
2522
|
+
function applyDeterministicReplacements(spec, ctx) {
|
|
2523
|
+
const effectiveComponents = buildEffectiveComponents(ctx);
|
|
2524
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2525
|
+
const preferredByCategory = /* @__PURE__ */ new Map();
|
|
2526
|
+
for (const entry of effectiveComponents) {
|
|
2527
|
+
const list = byName.get(entry.component.name) ?? [];
|
|
2528
|
+
list.push(entry);
|
|
2529
|
+
byName.set(entry.component.name, list);
|
|
2530
|
+
if (entry.selection !== "preferred") continue;
|
|
2531
|
+
const category = entry.component.category ?? "uncategorized";
|
|
2532
|
+
const names = preferredByCategory.get(category) ?? [];
|
|
2533
|
+
if (!names.includes(entry.component.name)) {
|
|
2534
|
+
names.push(entry.component.name);
|
|
2535
|
+
}
|
|
2536
|
+
preferredByCategory.set(category, names);
|
|
2537
|
+
}
|
|
2538
|
+
const fixedSpec = cloneSpec(spec);
|
|
2539
|
+
const nodes = Array.isArray(fixedSpec.nodes) ? fixedSpec.nodes : [];
|
|
2540
|
+
const replacements = [];
|
|
2541
|
+
for (const node of nodes) {
|
|
2542
|
+
if (!node.type) continue;
|
|
2543
|
+
const componentEntries = byName.get(node.type) ?? [];
|
|
2544
|
+
const component = componentEntries[0];
|
|
2545
|
+
if (!component || component.selection !== "discouraged" && component.selection !== "forbidden") {
|
|
2546
|
+
continue;
|
|
2547
|
+
}
|
|
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
|
+
replacements.push({
|
|
2552
|
+
nodeId: node.id ?? node.type,
|
|
2553
|
+
from: component.component.name,
|
|
2554
|
+
to: replacement,
|
|
2555
|
+
reason: "deprecated_component"
|
|
2556
|
+
});
|
|
2557
|
+
node.type = replacement;
|
|
2558
|
+
}
|
|
2559
|
+
return {
|
|
2560
|
+
fixedSpec,
|
|
2561
|
+
replacements
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
function buildSummary(args) {
|
|
2565
|
+
const lines = [
|
|
2566
|
+
`status: ${args.status}`,
|
|
2567
|
+
`original: ${formatVerdict2(args.originalVerdict, "summary")}`
|
|
2568
|
+
];
|
|
2569
|
+
if (args.replacements.length > 0) {
|
|
2570
|
+
lines.push(
|
|
2571
|
+
`replacements: ${args.replacements.map((item) => `${item.from} -> ${item.to}`).join(", ")}`
|
|
2572
|
+
);
|
|
2573
|
+
lines.push(`final: ${formatVerdict2(args.finalVerdict, "summary")}`);
|
|
2574
|
+
}
|
|
2575
|
+
return lines.join("\n");
|
|
2576
|
+
}
|
|
2577
|
+
var validateAndFixHandler = async (args, ctx) => {
|
|
2578
|
+
const spec = args?.spec;
|
|
2579
|
+
if (!spec || typeof spec !== "object") {
|
|
2580
|
+
return {
|
|
2581
|
+
content: [
|
|
2582
|
+
{
|
|
2583
|
+
type: "text",
|
|
2584
|
+
text: JSON.stringify({
|
|
2585
|
+
error: "spec is required and must be an object with { nodes: [{ id, type, props, children }] }"
|
|
2586
|
+
})
|
|
2587
|
+
}
|
|
2588
|
+
],
|
|
2589
|
+
isError: true
|
|
2590
|
+
};
|
|
2591
|
+
}
|
|
2592
|
+
const policyOverrides = args?.policy;
|
|
2593
|
+
const applyFixes = args?.applyFixes !== false;
|
|
2594
|
+
const format = args?.format ?? "json";
|
|
2595
|
+
try {
|
|
2596
|
+
const effectiveComponents = buildEffectiveComponents(ctx);
|
|
2597
|
+
const originalVerdict = await runGovern(spec, ctx, policyOverrides);
|
|
2598
|
+
let finalVerdict = originalVerdict;
|
|
2599
|
+
let fixedSpec;
|
|
2600
|
+
let replacements = [];
|
|
2601
|
+
if (!originalVerdict.passed && applyFixes) {
|
|
2602
|
+
const result = applyDeterministicReplacements(
|
|
2603
|
+
spec,
|
|
2604
|
+
ctx
|
|
2605
|
+
);
|
|
2606
|
+
replacements = result.replacements;
|
|
2607
|
+
fixedSpec = result.fixedSpec;
|
|
2608
|
+
if (replacements.length > 0) {
|
|
2609
|
+
finalVerdict = await runGovern(fixedSpec, ctx, policyOverrides);
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
const status = originalVerdict.passed ? "pass" : replacements.length > 0 ? "fixed" : "fail";
|
|
2613
|
+
const payload = {
|
|
2614
|
+
status,
|
|
2615
|
+
applyFixes,
|
|
2616
|
+
replacements,
|
|
2617
|
+
originalVerdict,
|
|
2618
|
+
finalVerdict,
|
|
2619
|
+
...fixedSpec ? { fixedSpec } : {},
|
|
2620
|
+
preferredComponents: getSelectionNames(
|
|
2621
|
+
effectiveComponents,
|
|
2622
|
+
"preferred"
|
|
2623
|
+
),
|
|
2624
|
+
discouragedComponents: getSelectionNames(effectiveComponents, [
|
|
2625
|
+
"discouraged",
|
|
2626
|
+
"forbidden"
|
|
2627
|
+
])
|
|
2628
|
+
};
|
|
2629
|
+
return {
|
|
2630
|
+
content: [
|
|
2631
|
+
{
|
|
2632
|
+
type: "text",
|
|
2633
|
+
text: format === "summary" ? buildSummary({
|
|
2634
|
+
status,
|
|
2635
|
+
originalVerdict,
|
|
2636
|
+
finalVerdict,
|
|
2637
|
+
replacements
|
|
2638
|
+
}) : JSON.stringify(payload)
|
|
2639
|
+
}
|
|
2640
|
+
],
|
|
2641
|
+
_meta: {
|
|
2642
|
+
status,
|
|
2643
|
+
replacementCount: replacements.length,
|
|
2644
|
+
passed: finalVerdict.passed
|
|
2645
|
+
}
|
|
2646
|
+
};
|
|
2647
|
+
} catch (error) {
|
|
2648
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2649
|
+
const isSpecError = message.includes("Expected") || message.includes("Required");
|
|
2650
|
+
return {
|
|
2651
|
+
content: [
|
|
2652
|
+
{
|
|
2653
|
+
type: "text",
|
|
2654
|
+
text: JSON.stringify({
|
|
2655
|
+
error: isSpecError ? `Invalid spec format: ${message}. Expected: { nodes: [{ id: string, type: string, props: object, children?: string[] }] }` : message
|
|
2656
|
+
})
|
|
2657
|
+
}
|
|
2658
|
+
],
|
|
2659
|
+
isError: true
|
|
2660
|
+
};
|
|
2661
|
+
}
|
|
2662
|
+
};
|
|
2663
|
+
|
|
2369
2664
|
// src/tools/generate-ui.ts
|
|
2370
2665
|
var generateUiHandler = async (args, ctx) => {
|
|
2371
2666
|
const prompt = args?.prompt;
|
|
@@ -2395,16 +2690,154 @@ var generateUiHandler = async (args, ctx) => {
|
|
|
2395
2690
|
};
|
|
2396
2691
|
};
|
|
2397
2692
|
|
|
2693
|
+
// src/findings-service.ts
|
|
2694
|
+
var DEFAULT_CLOUD_URL = "https://app.usefragments.com";
|
|
2695
|
+
function normalizeCloudUrl(url) {
|
|
2696
|
+
if (!url) return DEFAULT_CLOUD_URL;
|
|
2697
|
+
return url.replace(/\/+$/, "");
|
|
2698
|
+
}
|
|
2699
|
+
async function fetchFindings(apiKey, params, cloudUrl) {
|
|
2700
|
+
const base = normalizeCloudUrl(cloudUrl);
|
|
2701
|
+
const url = new URL(`${base}/api/findings`);
|
|
2702
|
+
if (params.status) url.searchParams.set("status", params.status);
|
|
2703
|
+
if (params.severity) url.searchParams.set("severity", params.severity);
|
|
2704
|
+
if (params.category) url.searchParams.set("category", params.category);
|
|
2705
|
+
if (params.ruleId) url.searchParams.set("ruleId", params.ruleId);
|
|
2706
|
+
if (params.filePath) url.searchParams.set("filePath", params.filePath);
|
|
2707
|
+
if (params.limit != null) url.searchParams.set("limit", String(params.limit));
|
|
2708
|
+
const response = await fetch(url.toString(), {
|
|
2709
|
+
headers: { "X-API-Key": apiKey }
|
|
2710
|
+
});
|
|
2711
|
+
if (!response.ok) {
|
|
2712
|
+
const body = await response.text();
|
|
2713
|
+
let message;
|
|
2714
|
+
try {
|
|
2715
|
+
const parsed = JSON.parse(body);
|
|
2716
|
+
message = parsed.error ?? body;
|
|
2717
|
+
} catch {
|
|
2718
|
+
message = body;
|
|
2719
|
+
}
|
|
2720
|
+
throw new Error(
|
|
2721
|
+
`Cloud findings API error (${response.status}): ${message}`
|
|
2722
|
+
);
|
|
2723
|
+
}
|
|
2724
|
+
return await response.json();
|
|
2725
|
+
}
|
|
2726
|
+
async function fetchFindingsForFile(apiKey, filePath, cloudUrl) {
|
|
2727
|
+
return fetchFindings(
|
|
2728
|
+
apiKey,
|
|
2729
|
+
{ status: "open", filePath, limit: 200 },
|
|
2730
|
+
cloudUrl
|
|
2731
|
+
);
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
// src/tools/findings.ts
|
|
2735
|
+
function resolveCloudApiKey(ctx) {
|
|
2736
|
+
return ctx.config.cloudApiKey ?? ctx.config.fileConfig?.cloud?.apiKey ?? process.env.FRAGMENTS_API_KEY;
|
|
2737
|
+
}
|
|
2738
|
+
function resolveCloudUrl(ctx) {
|
|
2739
|
+
return ctx.config.fileConfig?.cloud?.url ?? process.env.FRAGMENTS_CLOUD_URL;
|
|
2740
|
+
}
|
|
2741
|
+
function missingKeyError() {
|
|
2742
|
+
return {
|
|
2743
|
+
content: [
|
|
2744
|
+
{
|
|
2745
|
+
type: "text",
|
|
2746
|
+
text: JSON.stringify({
|
|
2747
|
+
error: "Cloud API key required. Set FRAGMENTS_API_KEY env var, pass --cloud-api-key, or configure cloud.apiKey in ds-mcp.config.json."
|
|
2748
|
+
})
|
|
2749
|
+
}
|
|
2750
|
+
],
|
|
2751
|
+
isError: true
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
var findingsListHandler = async (args, ctx) => {
|
|
2755
|
+
const apiKey = resolveCloudApiKey(ctx);
|
|
2756
|
+
if (!apiKey) return missingKeyError();
|
|
2757
|
+
const cloudUrl = resolveCloudUrl(ctx);
|
|
2758
|
+
const params = {};
|
|
2759
|
+
if (args.status) params.status = args.status;
|
|
2760
|
+
if (args.severity)
|
|
2761
|
+
params.severity = args.severity;
|
|
2762
|
+
if (args.category) params.category = String(args.category);
|
|
2763
|
+
if (args.ruleId) params.ruleId = String(args.ruleId);
|
|
2764
|
+
if (args.filePath) params.filePath = String(args.filePath);
|
|
2765
|
+
if (args.limit != null) params.limit = Number(args.limit);
|
|
2766
|
+
try {
|
|
2767
|
+
const result = await fetchFindings(apiKey, params, cloudUrl);
|
|
2768
|
+
return {
|
|
2769
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
2770
|
+
_meta: { count: result.findings.length }
|
|
2771
|
+
};
|
|
2772
|
+
} catch (error) {
|
|
2773
|
+
return {
|
|
2774
|
+
content: [
|
|
2775
|
+
{
|
|
2776
|
+
type: "text",
|
|
2777
|
+
text: JSON.stringify({
|
|
2778
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2779
|
+
})
|
|
2780
|
+
}
|
|
2781
|
+
],
|
|
2782
|
+
isError: true
|
|
2783
|
+
};
|
|
2784
|
+
}
|
|
2785
|
+
};
|
|
2786
|
+
var findingsForFileHandler = async (args, ctx) => {
|
|
2787
|
+
const apiKey = resolveCloudApiKey(ctx);
|
|
2788
|
+
if (!apiKey) return missingKeyError();
|
|
2789
|
+
const filePath = args.filePath;
|
|
2790
|
+
if (!filePath || typeof filePath !== "string") {
|
|
2791
|
+
return {
|
|
2792
|
+
content: [
|
|
2793
|
+
{
|
|
2794
|
+
type: "text",
|
|
2795
|
+
text: JSON.stringify({ error: "filePath is required." })
|
|
2796
|
+
}
|
|
2797
|
+
],
|
|
2798
|
+
isError: true
|
|
2799
|
+
};
|
|
2800
|
+
}
|
|
2801
|
+
const cloudUrl = resolveCloudUrl(ctx);
|
|
2802
|
+
try {
|
|
2803
|
+
const result = await fetchFindingsForFile(apiKey, filePath, cloudUrl);
|
|
2804
|
+
const findings = result.findings.filter((f) => f.filePath === filePath);
|
|
2805
|
+
return {
|
|
2806
|
+
content: [
|
|
2807
|
+
{
|
|
2808
|
+
type: "text",
|
|
2809
|
+
text: JSON.stringify({ findings, filePath })
|
|
2810
|
+
}
|
|
2811
|
+
],
|
|
2812
|
+
_meta: { count: findings.length, filePath }
|
|
2813
|
+
};
|
|
2814
|
+
} catch (error) {
|
|
2815
|
+
return {
|
|
2816
|
+
content: [
|
|
2817
|
+
{
|
|
2818
|
+
type: "text",
|
|
2819
|
+
text: JSON.stringify({
|
|
2820
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2821
|
+
})
|
|
2822
|
+
}
|
|
2823
|
+
],
|
|
2824
|
+
isError: true
|
|
2825
|
+
};
|
|
2826
|
+
}
|
|
2827
|
+
};
|
|
2828
|
+
|
|
2398
2829
|
// src/tools/index.ts
|
|
2399
2830
|
var CORE_TOOLS = {
|
|
2400
2831
|
discover: discoverHandler,
|
|
2401
2832
|
inspect: inspectHandler,
|
|
2402
2833
|
blocks: blocksHandler,
|
|
2403
2834
|
tokens: tokensHandler,
|
|
2404
|
-
implement: implementHandler,
|
|
2405
2835
|
graph: graphHandler,
|
|
2406
2836
|
perf: perfHandler,
|
|
2407
|
-
govern: governHandler
|
|
2837
|
+
govern: governHandler,
|
|
2838
|
+
validate_and_fix: validateAndFixHandler,
|
|
2839
|
+
findings_list: findingsListHandler,
|
|
2840
|
+
findings_for_file: findingsForFileHandler
|
|
2408
2841
|
};
|
|
2409
2842
|
var VIEWER_TOOLS = {
|
|
2410
2843
|
render: renderHandler,
|
|
@@ -2424,9 +2857,9 @@ var TOOL_CAPABILITIES = {
|
|
|
2424
2857
|
inspect: ["components"],
|
|
2425
2858
|
blocks: ["blocks"],
|
|
2426
2859
|
tokens: ["tokens"],
|
|
2427
|
-
implement: ["components"],
|
|
2428
2860
|
graph: ["graph"],
|
|
2429
2861
|
perf: ["performance"],
|
|
2862
|
+
validate_and_fix: ["components"],
|
|
2430
2863
|
render: ["components"],
|
|
2431
2864
|
fix: ["components"],
|
|
2432
2865
|
a11y: ["components"]
|
|
@@ -2576,7 +3009,7 @@ import { join as join7 } from "path";
|
|
|
2576
3009
|
import { readFile } from "fs/promises";
|
|
2577
3010
|
|
|
2578
3011
|
// src/discovery.ts
|
|
2579
|
-
import { existsSync as existsSync3, readFileSync as
|
|
3012
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync } from "fs";
|
|
2580
3013
|
import { join as join3, dirname, resolve } from "path";
|
|
2581
3014
|
import { createRequire } from "module";
|
|
2582
3015
|
function resolveWorkspaceGlob(baseDir, pattern) {
|
|
@@ -2609,7 +3042,7 @@ function getWorkspaceDirs(rootDir) {
|
|
|
2609
3042
|
const rootPkgPath = join3(rootDir, "package.json");
|
|
2610
3043
|
if (existsSync3(rootPkgPath)) {
|
|
2611
3044
|
try {
|
|
2612
|
-
const rootPkg = JSON.parse(
|
|
3045
|
+
const rootPkg = JSON.parse(readFileSync3(rootPkgPath, "utf-8"));
|
|
2613
3046
|
const workspaces = Array.isArray(rootPkg.workspaces) ? rootPkg.workspaces : rootPkg.workspaces?.packages;
|
|
2614
3047
|
if (Array.isArray(workspaces)) {
|
|
2615
3048
|
for (const pattern of workspaces) {
|
|
@@ -2623,7 +3056,7 @@ function getWorkspaceDirs(rootDir) {
|
|
|
2623
3056
|
const pnpmWsPath = join3(rootDir, "pnpm-workspace.yaml");
|
|
2624
3057
|
if (existsSync3(pnpmWsPath)) {
|
|
2625
3058
|
try {
|
|
2626
|
-
const content =
|
|
3059
|
+
const content = readFileSync3(pnpmWsPath, "utf-8");
|
|
2627
3060
|
const lines = content.split("\n");
|
|
2628
3061
|
let inPackages = false;
|
|
2629
3062
|
for (const line of lines) {
|
|
@@ -2656,7 +3089,7 @@ function resolveDepPackageJson(localRequire, depName) {
|
|
|
2656
3089
|
while (true) {
|
|
2657
3090
|
const candidate = join3(dir, "package.json");
|
|
2658
3091
|
if (existsSync3(candidate)) {
|
|
2659
|
-
const pkg = JSON.parse(
|
|
3092
|
+
const pkg = JSON.parse(readFileSync3(candidate, "utf-8"));
|
|
2660
3093
|
if (pkg.name === depName) return candidate;
|
|
2661
3094
|
}
|
|
2662
3095
|
const parent = dirname(dir);
|
|
@@ -2671,7 +3104,7 @@ function findFragmentsInDeps(dir, found, depField) {
|
|
|
2671
3104
|
const pkgJsonPath = join3(dir, "package.json");
|
|
2672
3105
|
if (!existsSync3(pkgJsonPath)) return;
|
|
2673
3106
|
try {
|
|
2674
|
-
const pkgJson = JSON.parse(
|
|
3107
|
+
const pkgJson = JSON.parse(readFileSync3(pkgJsonPath, "utf-8"));
|
|
2675
3108
|
const allDeps = {
|
|
2676
3109
|
...pkgJson.dependencies,
|
|
2677
3110
|
...pkgJson.devDependencies
|
|
@@ -2681,7 +3114,7 @@ function findFragmentsInDeps(dir, found, depField) {
|
|
|
2681
3114
|
try {
|
|
2682
3115
|
const depPkgPath = resolveDepPackageJson(localRequire, depName);
|
|
2683
3116
|
if (!depPkgPath) continue;
|
|
2684
|
-
const depPkg = JSON.parse(
|
|
3117
|
+
const depPkg = JSON.parse(readFileSync3(depPkgPath, "utf-8"));
|
|
2685
3118
|
if (depPkg[depField]) {
|
|
2686
3119
|
const fragmentsPath = join3(dirname(depPkgPath), depPkg[depField]);
|
|
2687
3120
|
if (existsSync3(fragmentsPath) && !found.includes(fragmentsPath)) {
|
|
@@ -2757,6 +3190,26 @@ function valueToString(value) {
|
|
|
2757
3190
|
return void 0;
|
|
2758
3191
|
}
|
|
2759
3192
|
}
|
|
3193
|
+
function buildCompoundChildren(contract, ai) {
|
|
3194
|
+
const contractChildren = Object.entries(contract?.compoundChildren ?? {});
|
|
3195
|
+
if (contractChildren.length > 0) {
|
|
3196
|
+
return contractChildren.map(([childName, child]) => ({
|
|
3197
|
+
name: childName,
|
|
3198
|
+
description: child.description,
|
|
3199
|
+
required: child.required,
|
|
3200
|
+
accepts: child.accepts,
|
|
3201
|
+
visibility: "public"
|
|
3202
|
+
}));
|
|
3203
|
+
}
|
|
3204
|
+
const subs = ai?.subComponents;
|
|
3205
|
+
if (!subs || subs.length === 0) return [];
|
|
3206
|
+
const requiredSet = new Set(ai?.requiredChildren ?? []);
|
|
3207
|
+
return subs.map((name) => ({
|
|
3208
|
+
name,
|
|
3209
|
+
required: requiredSet.has(name) || void 0,
|
|
3210
|
+
visibility: "public"
|
|
3211
|
+
}));
|
|
3212
|
+
}
|
|
2760
3213
|
function componentFromCompiledFragment(args) {
|
|
2761
3214
|
const { fragment, sourceType } = args;
|
|
2762
3215
|
const name = fragment.meta.name;
|
|
@@ -2806,15 +3259,7 @@ function componentFromCompiledFragment(args) {
|
|
|
2806
3259
|
relationship: relation.relationship,
|
|
2807
3260
|
note: relation.note
|
|
2808
3261
|
})),
|
|
2809
|
-
compoundChildren:
|
|
2810
|
-
([childName, child]) => ({
|
|
2811
|
-
name: childName,
|
|
2812
|
-
description: child.description,
|
|
2813
|
-
required: child.required,
|
|
2814
|
-
accepts: child.accepts,
|
|
2815
|
-
visibility: "public"
|
|
2816
|
-
})
|
|
2817
|
-
),
|
|
3262
|
+
compoundChildren: buildCompoundChildren(contract, ai),
|
|
2818
3263
|
guidance: {
|
|
2819
3264
|
when: usage.when ?? [],
|
|
2820
3265
|
whenNot: usage.whenNot ?? [],
|
|
@@ -3010,7 +3455,7 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
3010
3455
|
};
|
|
3011
3456
|
|
|
3012
3457
|
// src/adapters/auto-extract.ts
|
|
3013
|
-
import { existsSync as existsSync6, readFileSync as
|
|
3458
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
3014
3459
|
import { join as join6, relative, sep } from "path";
|
|
3015
3460
|
|
|
3016
3461
|
// src/adapters/discover-components.ts
|
|
@@ -3098,7 +3543,7 @@ function inferComponentName(fileName, dirPath) {
|
|
|
3098
3543
|
}
|
|
3099
3544
|
|
|
3100
3545
|
// src/adapters/scan-tokens.ts
|
|
3101
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
3546
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
|
|
3102
3547
|
import { join as join5, extname as extname2 } from "path";
|
|
3103
3548
|
function scanTokens(projectRoot) {
|
|
3104
3549
|
const cssFiles = discoverCssFiles(projectRoot);
|
|
@@ -3107,7 +3552,7 @@ function scanTokens(projectRoot) {
|
|
|
3107
3552
|
let prefix = "";
|
|
3108
3553
|
for (const filePath of cssFiles) {
|
|
3109
3554
|
try {
|
|
3110
|
-
const content =
|
|
3555
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
3111
3556
|
const tokens = extractCustomProperties(content);
|
|
3112
3557
|
allTokens.push(...tokens);
|
|
3113
3558
|
} catch {
|
|
@@ -3274,7 +3719,7 @@ var AutoExtractionAdapter = class {
|
|
|
3274
3719
|
async load(projectRoot) {
|
|
3275
3720
|
let extractMod;
|
|
3276
3721
|
try {
|
|
3277
|
-
extractMod = await
|
|
3722
|
+
extractMod = await loadExtractorModule();
|
|
3278
3723
|
} catch (e) {
|
|
3279
3724
|
throw new Error(
|
|
3280
3725
|
"Auto-extraction requires @fragments-sdk/extract and TypeScript.\n\nIf you see this error, the MCP server may not be installed correctly.\nAlternative: pre-build your design system with `npx fragments build`"
|
|
@@ -3390,6 +3835,13 @@ Check that your tsconfig.json includes the component directories.`
|
|
|
3390
3835
|
}
|
|
3391
3836
|
}
|
|
3392
3837
|
};
|
|
3838
|
+
var extractorModulePromise = null;
|
|
3839
|
+
async function loadExtractorModule() {
|
|
3840
|
+
if (!extractorModulePromise) {
|
|
3841
|
+
extractorModulePromise = import("./dist-V7D67NXS.js");
|
|
3842
|
+
}
|
|
3843
|
+
return extractorModulePromise;
|
|
3844
|
+
}
|
|
3393
3845
|
var UNIVERSAL_INHERITED = /* @__PURE__ */ new Set(["children", "className", "id", "disabled"]);
|
|
3394
3846
|
var FORM_INPUT_INHERITED = /* @__PURE__ */ new Set([
|
|
3395
3847
|
"placeholder",
|
|
@@ -3661,7 +4113,7 @@ function readPackageName(projectRoot) {
|
|
|
3661
4113
|
try {
|
|
3662
4114
|
const pkgPath = join6(projectRoot, "package.json");
|
|
3663
4115
|
if (!existsSync6(pkgPath)) return void 0;
|
|
3664
|
-
const pkg = JSON.parse(
|
|
4116
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
3665
4117
|
return pkg.name;
|
|
3666
4118
|
} catch {
|
|
3667
4119
|
return void 0;
|
|
@@ -3669,16 +4121,53 @@ function readPackageName(projectRoot) {
|
|
|
3669
4121
|
}
|
|
3670
4122
|
|
|
3671
4123
|
// src/adapters/cloud-catalog.ts
|
|
3672
|
-
var
|
|
4124
|
+
var DEFAULT_CLOUD_URL2 = "https://app.usefragments.com/api/catalog";
|
|
4125
|
+
var TOKEN_CATEGORY_ALIASES2 = {
|
|
4126
|
+
color: ["color", "colors", "accent", "background", "foreground", "danger", "brand"],
|
|
4127
|
+
spacing: ["spacing", "space", "padding", "margin", "gap", "inset"],
|
|
4128
|
+
typography: ["typography", "font", "text", "copy", "line-height", "letter"],
|
|
4129
|
+
border: ["border", "borders", "stroke", "outline"],
|
|
4130
|
+
radius: ["radius", "radii", "corner", "corners", "rounded"],
|
|
4131
|
+
shadow: ["shadow", "shadows", "elevation"],
|
|
4132
|
+
layout: ["layout", "grid", "container", "breakpoint"],
|
|
4133
|
+
focus: ["focus", "ring", "focus-ring"],
|
|
4134
|
+
surface: ["surface", "surfaces", "canvas", "card", "background"]
|
|
4135
|
+
};
|
|
3673
4136
|
function normalizeCatalogUrl(url) {
|
|
3674
|
-
if (!url) return
|
|
4137
|
+
if (!url) return DEFAULT_CLOUD_URL2;
|
|
3675
4138
|
if (url.endsWith("/api/catalog")) return url;
|
|
3676
4139
|
return `${url.replace(/\/+$/, "")}/api/catalog`;
|
|
3677
4140
|
}
|
|
4141
|
+
function normalizeValidateFixUrl(url) {
|
|
4142
|
+
const base = normalizeCatalogUrl(url).replace(/\/api\/catalog$/, "");
|
|
4143
|
+
return `${base}/api/context?target=validate-fix`;
|
|
4144
|
+
}
|
|
4145
|
+
function normalizeValue(value) {
|
|
4146
|
+
return value?.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim() ?? "";
|
|
4147
|
+
}
|
|
4148
|
+
function canonicalizeTokenCategory(token) {
|
|
4149
|
+
const candidates = [
|
|
4150
|
+
token.category,
|
|
4151
|
+
token.path?.[0],
|
|
4152
|
+
token.name.split(/[.:/-]/)[0]
|
|
4153
|
+
].map(normalizeValue).filter(Boolean);
|
|
4154
|
+
for (const candidate of candidates) {
|
|
4155
|
+
for (const [canonical, aliases] of Object.entries(
|
|
4156
|
+
TOKEN_CATEGORY_ALIASES2
|
|
4157
|
+
)) {
|
|
4158
|
+
if (candidate === canonical || aliases.some(
|
|
4159
|
+
(alias) => candidate === alias || candidate.includes(alias) || alias.includes(candidate)
|
|
4160
|
+
)) {
|
|
4161
|
+
return canonical;
|
|
4162
|
+
}
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4165
|
+
return candidates[0] || "other";
|
|
4166
|
+
}
|
|
3678
4167
|
function groupTokens(flat) {
|
|
3679
4168
|
const categories = {};
|
|
3680
4169
|
const normalizedFlat = (flat ?? []).map((token) => {
|
|
3681
|
-
const category = token
|
|
4170
|
+
const category = canonicalizeTokenCategory(token);
|
|
3682
4171
|
const normalized = {
|
|
3683
4172
|
name: token.name,
|
|
3684
4173
|
category,
|
|
@@ -3761,6 +4250,7 @@ function mapComponent(component, designSystem) {
|
|
|
3761
4250
|
importPath: designSystem?.importPath ?? designSystem?.packageName ?? void 0,
|
|
3762
4251
|
publicRef: component.publicRef,
|
|
3763
4252
|
publicSlug: component.publicSlug ?? null,
|
|
4253
|
+
isCanonical: component.isCanonical ?? false,
|
|
3764
4254
|
tier: component.tier,
|
|
3765
4255
|
parentComponentId: component.parentComponentKey,
|
|
3766
4256
|
parentComponentName: component.parentComponentName,
|
|
@@ -3770,17 +4260,54 @@ function mapComponent(component, designSystem) {
|
|
|
3770
4260
|
}
|
|
3771
4261
|
};
|
|
3772
4262
|
}
|
|
4263
|
+
function normalizeValidateFixContext(raw) {
|
|
4264
|
+
const content = raw.content;
|
|
4265
|
+
if (!content || !Array.isArray(content.components)) {
|
|
4266
|
+
return void 0;
|
|
4267
|
+
}
|
|
4268
|
+
const components = content.components.filter(
|
|
4269
|
+
(component) => Boolean(component?.componentKey) && Boolean(component?.publicRef) && Boolean(component?.name) && Boolean(component?.selection)
|
|
4270
|
+
).map((component) => ({
|
|
4271
|
+
componentKey: component.componentKey,
|
|
4272
|
+
publicRef: component.publicRef,
|
|
4273
|
+
name: component.name,
|
|
4274
|
+
category: component.category ?? "uncategorized",
|
|
4275
|
+
status: component.status ?? "stable",
|
|
4276
|
+
tier: component.tier ?? "composition",
|
|
4277
|
+
isCanonical: component.isCanonical ?? false,
|
|
4278
|
+
isActive: component.isActive ?? true,
|
|
4279
|
+
selection: component.selection ?? "allowed",
|
|
4280
|
+
reasons: component.reasons ?? [],
|
|
4281
|
+
usageGuidance: component.usageGuidance,
|
|
4282
|
+
dos: component.dos ?? [],
|
|
4283
|
+
donts: component.donts ?? []
|
|
4284
|
+
}));
|
|
4285
|
+
return {
|
|
4286
|
+
version: 1,
|
|
4287
|
+
catalogRevision: content.catalogRevision,
|
|
4288
|
+
updatedAt: content.updatedAt,
|
|
4289
|
+
policy: {
|
|
4290
|
+
mode: "cloud",
|
|
4291
|
+
endpoint: content.policy?.endpoint ?? "/api/govern/policy"
|
|
4292
|
+
},
|
|
4293
|
+
components
|
|
4294
|
+
};
|
|
4295
|
+
}
|
|
3773
4296
|
var CloudCatalogAdapter = class {
|
|
3774
4297
|
constructor(options) {
|
|
3775
4298
|
this.options = options;
|
|
3776
4299
|
}
|
|
3777
4300
|
name = "cloud";
|
|
3778
4301
|
async load(_projectRoot) {
|
|
3779
|
-
const
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
4302
|
+
const headers = {
|
|
4303
|
+
"X-API-Key": this.options.apiKey
|
|
4304
|
+
};
|
|
4305
|
+
const [response, validateFixResponse] = await Promise.all([
|
|
4306
|
+
fetch(normalizeCatalogUrl(this.options.url), { headers }),
|
|
4307
|
+
fetch(normalizeValidateFixUrl(this.options.url), { headers }).catch(
|
|
4308
|
+
() => null
|
|
4309
|
+
)
|
|
4310
|
+
]);
|
|
3784
4311
|
if (!response.ok) {
|
|
3785
4312
|
throw new Error(
|
|
3786
4313
|
`Failed to load Cloud catalog (${response.status} ${response.statusText}).`
|
|
@@ -3822,15 +4349,31 @@ var CloudCatalogAdapter = class {
|
|
|
3822
4349
|
packageMap,
|
|
3823
4350
|
defaultPackageName: packageName
|
|
3824
4351
|
});
|
|
4352
|
+
const validateFixContext = validateFixResponse && validateFixResponse.ok ? normalizeValidateFixContext(
|
|
4353
|
+
await validateFixResponse.json()
|
|
4354
|
+
) : void 0;
|
|
4355
|
+
const hydratedComponents = Object.fromEntries(
|
|
4356
|
+
Object.entries(snapshot.components).map(([componentId, component]) => [
|
|
4357
|
+
componentId,
|
|
4358
|
+
{
|
|
4359
|
+
...component,
|
|
4360
|
+
isCanonical: components[componentId]?.isCanonical ?? false
|
|
4361
|
+
}
|
|
4362
|
+
])
|
|
4363
|
+
);
|
|
3825
4364
|
return {
|
|
3826
|
-
snapshot
|
|
3827
|
-
|
|
4365
|
+
snapshot: {
|
|
4366
|
+
...snapshot,
|
|
4367
|
+
components: hydratedComponents
|
|
4368
|
+
},
|
|
4369
|
+
components: hydratedComponents,
|
|
3828
4370
|
blocks: snapshot.blocks,
|
|
3829
4371
|
tokens: snapshot.tokens,
|
|
3830
4372
|
graph: void 0,
|
|
3831
4373
|
performanceSummary: void 0,
|
|
3832
4374
|
packageMap: snapshot.packageMap,
|
|
3833
4375
|
defaultPackageName: snapshot.defaultPackageName,
|
|
4376
|
+
validateFixContext,
|
|
3834
4377
|
capabilities: new Set(snapshot.capabilities)
|
|
3835
4378
|
};
|
|
3836
4379
|
}
|
|
@@ -3908,6 +4451,7 @@ function normalizeRelations(relations, fallback) {
|
|
|
3908
4451
|
function buildComponent(manifest, entry, shard) {
|
|
3909
4452
|
const sourcePackage = manifest.designSystem.importPath ?? manifest.designSystem.packageName ?? void 0;
|
|
3910
4453
|
const component = shard.component;
|
|
4454
|
+
const componentRecord = component;
|
|
3911
4455
|
const props = normalizeProps(component.props);
|
|
3912
4456
|
return {
|
|
3913
4457
|
id: shard.componentId,
|
|
@@ -3944,6 +4488,7 @@ function buildComponent(manifest, entry, shard) {
|
|
|
3944
4488
|
importPath: sourcePackage,
|
|
3945
4489
|
publicRef: component.publicRef,
|
|
3946
4490
|
publicSlug: component.publicSlug,
|
|
4491
|
+
isCanonical: componentRecord.isCanonical ?? component.tier === "core",
|
|
3947
4492
|
tier: component.tier,
|
|
3948
4493
|
parentComponentName: component.parentComponentName,
|
|
3949
4494
|
metadata: {
|
|
@@ -4036,9 +4581,21 @@ var BundleAdapter = class {
|
|
|
4036
4581
|
packageMap,
|
|
4037
4582
|
defaultPackageName: packageName
|
|
4038
4583
|
});
|
|
4584
|
+
const hydratedComponents = Object.fromEntries(
|
|
4585
|
+
Object.entries(snapshot.components).map(([componentId, component]) => [
|
|
4586
|
+
componentId,
|
|
4587
|
+
{
|
|
4588
|
+
...component,
|
|
4589
|
+
isCanonical: components[componentId]?.isCanonical ?? component.tier === "core"
|
|
4590
|
+
}
|
|
4591
|
+
])
|
|
4592
|
+
);
|
|
4039
4593
|
return {
|
|
4040
|
-
snapshot
|
|
4041
|
-
|
|
4594
|
+
snapshot: {
|
|
4595
|
+
...snapshot,
|
|
4596
|
+
components: hydratedComponents
|
|
4597
|
+
},
|
|
4598
|
+
components: hydratedComponents,
|
|
4042
4599
|
blocks: snapshot.blocks,
|
|
4043
4600
|
tokens: snapshot.tokens,
|
|
4044
4601
|
graph: snapshot.graph,
|
|
@@ -4051,10 +4608,10 @@ var BundleAdapter = class {
|
|
|
4051
4608
|
};
|
|
4052
4609
|
|
|
4053
4610
|
// src/source-selection.ts
|
|
4054
|
-
function
|
|
4611
|
+
function resolveCloudApiKey2(config, fileConfig) {
|
|
4055
4612
|
return config.cloudApiKey ?? fileConfig?.cloud?.apiKey ?? process.env.FRAGMENTS_API_KEY;
|
|
4056
4613
|
}
|
|
4057
|
-
function
|
|
4614
|
+
function resolveCloudUrl2(fileConfig) {
|
|
4058
4615
|
return fileConfig?.cloud?.url ?? process.env.FRAGMENTS_CLOUD_URL;
|
|
4059
4616
|
}
|
|
4060
4617
|
function hasTsProject(projectRoot) {
|
|
@@ -4064,8 +4621,8 @@ function resolveDataAdapter(config, fileConfig) {
|
|
|
4064
4621
|
const source = config.source ?? fileConfig?.source ?? "auto";
|
|
4065
4622
|
const fragmentsJsonPaths = findFragmentsJson(config.projectRoot);
|
|
4066
4623
|
const bundleManifestPaths = findBundleManifest(config.projectRoot);
|
|
4067
|
-
const cloudApiKey =
|
|
4068
|
-
const cloudUrl =
|
|
4624
|
+
const cloudApiKey = resolveCloudApiKey2(config, fileConfig);
|
|
4625
|
+
const cloudUrl = resolveCloudUrl2(fileConfig);
|
|
4069
4626
|
switch (source) {
|
|
4070
4627
|
case "fragments-json":
|
|
4071
4628
|
return { adapter: new FragmentsJsonAdapter(), mode: "fragments-json" };
|
|
@@ -4122,6 +4679,9 @@ function resolveSearchApiKey(config, fileConfig) {
|
|
|
4122
4679
|
// src/server.ts
|
|
4123
4680
|
var TOOL_NAMES = buildToolNames();
|
|
4124
4681
|
var TOOLS = buildMcpTools();
|
|
4682
|
+
var TOOL_DEFINITION_BY_KEY = new Map(
|
|
4683
|
+
MCP_TOOL_DEFINITIONS.map((definition) => [definition.key, definition])
|
|
4684
|
+
);
|
|
4125
4685
|
function createMcpServer(config) {
|
|
4126
4686
|
const server = new Server(
|
|
4127
4687
|
{
|
|
@@ -4157,38 +4717,53 @@ function createMcpServer(config) {
|
|
|
4157
4717
|
}
|
|
4158
4718
|
}
|
|
4159
4719
|
let cachedData = null;
|
|
4720
|
+
let loadDataPromise = null;
|
|
4160
4721
|
let resolvedRoot = null;
|
|
4722
|
+
let resolveProjectRootPromise = null;
|
|
4161
4723
|
let componentIndex = null;
|
|
4162
4724
|
let blockIndex = null;
|
|
4163
4725
|
let tokenIndex = null;
|
|
4164
4726
|
async function resolveProjectRoot() {
|
|
4165
4727
|
if (resolvedRoot) return resolvedRoot;
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
const
|
|
4170
|
-
|
|
4171
|
-
|
|
4728
|
+
if (resolveProjectRootPromise) return resolveProjectRootPromise;
|
|
4729
|
+
resolveProjectRootPromise = (async () => {
|
|
4730
|
+
try {
|
|
4731
|
+
const result = await server.listRoots();
|
|
4732
|
+
if (result.roots?.length > 0) {
|
|
4733
|
+
const rootUri = result.roots[0].uri;
|
|
4734
|
+
resolvedRoot = fileURLToPath(rootUri);
|
|
4735
|
+
return resolvedRoot;
|
|
4736
|
+
}
|
|
4737
|
+
} catch {
|
|
4172
4738
|
}
|
|
4173
|
-
|
|
4739
|
+
resolvedRoot = config.projectRoot;
|
|
4740
|
+
return resolvedRoot;
|
|
4741
|
+
})();
|
|
4742
|
+
try {
|
|
4743
|
+
return await resolveProjectRootPromise;
|
|
4744
|
+
} finally {
|
|
4745
|
+
resolveProjectRootPromise = null;
|
|
4174
4746
|
}
|
|
4175
|
-
resolvedRoot = config.projectRoot;
|
|
4176
|
-
return resolvedRoot;
|
|
4177
4747
|
}
|
|
4178
4748
|
async function loadData() {
|
|
4179
4749
|
if (cachedData) return cachedData;
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4750
|
+
if (loadDataPromise) return loadDataPromise;
|
|
4751
|
+
loadDataPromise = (async () => {
|
|
4752
|
+
const projectRoot = await resolveProjectRoot();
|
|
4753
|
+
const loaded = await adapter.load(projectRoot);
|
|
4754
|
+
const allFragments = Object.values(loaded.components);
|
|
4755
|
+
const allBlocks = Object.values(loaded.blocks ?? {});
|
|
4756
|
+
componentIndex = buildComponentIndex(allFragments);
|
|
4757
|
+
blockIndex = allBlocks.length > 0 ? buildBlockIndex(allBlocks) : null;
|
|
4758
|
+
tokenIndex = loaded.tokens && loaded.tokens.total > 0 ? buildTokenIndex(loaded.tokens) : null;
|
|
4759
|
+
cachedData = loaded;
|
|
4760
|
+
return loaded;
|
|
4761
|
+
})();
|
|
4762
|
+
try {
|
|
4763
|
+
return await loadDataPromise;
|
|
4764
|
+
} finally {
|
|
4765
|
+
loadDataPromise = null;
|
|
4190
4766
|
}
|
|
4191
|
-
return cachedData;
|
|
4192
4767
|
}
|
|
4193
4768
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
4194
4769
|
const data = await loadData();
|
|
@@ -4220,7 +4795,7 @@ function createMcpServer(config) {
|
|
|
4220
4795
|
const packageJsonPath = join8(root, "package.json");
|
|
4221
4796
|
if (existsSync9(packageJsonPath)) {
|
|
4222
4797
|
try {
|
|
4223
|
-
const content =
|
|
4798
|
+
const content = readFileSync6(packageJsonPath, "utf-8");
|
|
4224
4799
|
const pkg = JSON.parse(content);
|
|
4225
4800
|
if (pkg.name) {
|
|
4226
4801
|
return pkg.name;
|
|
@@ -4234,6 +4809,23 @@ function createMcpServer(config) {
|
|
|
4234
4809
|
};
|
|
4235
4810
|
try {
|
|
4236
4811
|
const toolKey = registry.resolveKey(name);
|
|
4812
|
+
const definition = TOOL_DEFINITION_BY_KEY.get(toolKey);
|
|
4813
|
+
const argumentKeys = Object.keys(args ?? {});
|
|
4814
|
+
const allowedKeys = new Set(Object.keys(definition?.params ?? {}));
|
|
4815
|
+
const unknownKeys = definition ? argumentKeys.filter((key) => !allowedKeys.has(key)) : [];
|
|
4816
|
+
if (unknownKeys.length > 0) {
|
|
4817
|
+
return {
|
|
4818
|
+
content: [
|
|
4819
|
+
{
|
|
4820
|
+
type: "text",
|
|
4821
|
+
text: JSON.stringify({
|
|
4822
|
+
error: `Unknown argument(s) for ${toolKey}: ${unknownKeys.join(", ")}`
|
|
4823
|
+
})
|
|
4824
|
+
}
|
|
4825
|
+
],
|
|
4826
|
+
isError: true
|
|
4827
|
+
};
|
|
4828
|
+
}
|
|
4237
4829
|
const mCtx = {
|
|
4238
4830
|
toolName: name,
|
|
4239
4831
|
toolKey,
|
|
@@ -4290,4 +4882,4 @@ export {
|
|
|
4290
4882
|
startMcpServer,
|
|
4291
4883
|
createSandboxServer
|
|
4292
4884
|
};
|
|
4293
|
-
//# sourceMappingURL=chunk-
|
|
4885
|
+
//# sourceMappingURL=chunk-6JMX4AMO.js.map
|