@wallarm-org/mcp 3.2.0 → 3.3.0-rc-fix-claude-code.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -6
- package/dist/index.js +1245 -104
- package/dist/metadata/components.json +48840 -0
- package/dist/metadata/guidelines.json +1705 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mc
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import fs from "fs";
|
|
5
5
|
import path_0 from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
6
7
|
function getEnumValues(entries) {
|
|
7
8
|
const numericValues = Object.values(entries).filter((v)=>"number" == typeof v);
|
|
8
9
|
const values = Object.entries(entries).filter(([k, _])=>-1 === numericValues.indexOf(+k)).map(([_, v])=>v);
|
|
@@ -40,6 +41,13 @@ function cleanRegex(source) {
|
|
|
40
41
|
const end = source.endsWith("$") ? source.length - 1 : source.length;
|
|
41
42
|
return source.slice(start, end);
|
|
42
43
|
}
|
|
44
|
+
function floatSafeRemainder(val, step) {
|
|
45
|
+
const ratio = val / step;
|
|
46
|
+
const roundedRatio = Math.round(ratio);
|
|
47
|
+
const tolerance = 4 * Number.EPSILON * Math.max(Math.abs(ratio), 1);
|
|
48
|
+
if (Math.abs(ratio - roundedRatio) < tolerance) return 0;
|
|
49
|
+
return ratio - roundedRatio;
|
|
50
|
+
}
|
|
43
51
|
function util_assignProp(target, prop, value) {
|
|
44
52
|
Object.defineProperty(target, prop, {
|
|
45
53
|
value,
|
|
@@ -961,6 +969,7 @@ function datetime(args) {
|
|
|
961
969
|
return new RegExp(`^${dateSource}T(?:${timeRegex})$`);
|
|
962
970
|
}
|
|
963
971
|
const anyString = /^[\s\S]{0,}$/;
|
|
972
|
+
const number = /^-?\d+(?:\.\d+)?$/;
|
|
964
973
|
const regexes_boolean = /^(?:true|false)$/i;
|
|
965
974
|
const lowercase = /^[^A-Z]*$/;
|
|
966
975
|
const uppercase = /^[^a-z]*$/;
|
|
@@ -974,6 +983,120 @@ const _whenHasLength = (payload)=>{
|
|
|
974
983
|
const val = payload.value;
|
|
975
984
|
return !nullish(val) && void 0 !== val.length;
|
|
976
985
|
};
|
|
986
|
+
const numericOriginMap = {
|
|
987
|
+
number: "number",
|
|
988
|
+
bigint: "bigint",
|
|
989
|
+
object: "date"
|
|
990
|
+
};
|
|
991
|
+
const $ZodCheckLessThan = /*@__PURE__*/ $constructor("$ZodCheckLessThan", (inst, def)=>{
|
|
992
|
+
$ZodCheck.init(inst, def);
|
|
993
|
+
const origin = numericOriginMap[typeof def.value];
|
|
994
|
+
inst._zod.check = (payload)=>{
|
|
995
|
+
if (def.inclusive ? payload.value <= def.value : payload.value < def.value) return;
|
|
996
|
+
payload.issues.push({
|
|
997
|
+
origin: numericOriginMap[typeof payload.value] ?? origin,
|
|
998
|
+
code: "too_big",
|
|
999
|
+
maximum: "object" == typeof def.value ? def.value.getTime() : def.value,
|
|
1000
|
+
input: payload.value,
|
|
1001
|
+
inclusive: def.inclusive,
|
|
1002
|
+
inst,
|
|
1003
|
+
continue: !def.abort
|
|
1004
|
+
});
|
|
1005
|
+
};
|
|
1006
|
+
});
|
|
1007
|
+
const $ZodCheckGreaterThan = /*@__PURE__*/ $constructor("$ZodCheckGreaterThan", (inst, def)=>{
|
|
1008
|
+
$ZodCheck.init(inst, def);
|
|
1009
|
+
const origin = numericOriginMap[typeof def.value];
|
|
1010
|
+
inst._zod.check = (payload)=>{
|
|
1011
|
+
if (def.inclusive ? payload.value >= def.value : payload.value > def.value) return;
|
|
1012
|
+
payload.issues.push({
|
|
1013
|
+
origin: numericOriginMap[typeof payload.value] ?? origin,
|
|
1014
|
+
code: "too_small",
|
|
1015
|
+
minimum: "object" == typeof def.value ? def.value.getTime() : def.value,
|
|
1016
|
+
input: payload.value,
|
|
1017
|
+
inclusive: def.inclusive,
|
|
1018
|
+
inst,
|
|
1019
|
+
continue: !def.abort
|
|
1020
|
+
});
|
|
1021
|
+
};
|
|
1022
|
+
});
|
|
1023
|
+
const $ZodCheckMultipleOf = /*@__PURE__*/ $constructor("$ZodCheckMultipleOf", (inst, def)=>{
|
|
1024
|
+
$ZodCheck.init(inst, def);
|
|
1025
|
+
inst._zod.check = (payload)=>{
|
|
1026
|
+
if (typeof payload.value !== typeof def.value) throw new Error("Cannot mix number and bigint in multiple_of check.");
|
|
1027
|
+
const isMultiple = "bigint" == typeof payload.value ? def.value !== BigInt(0) && payload.value % def.value === BigInt(0) : 0 === floatSafeRemainder(payload.value, def.value);
|
|
1028
|
+
if (isMultiple) return;
|
|
1029
|
+
payload.issues.push({
|
|
1030
|
+
origin: typeof payload.value,
|
|
1031
|
+
code: "not_multiple_of",
|
|
1032
|
+
divisor: def.value,
|
|
1033
|
+
input: payload.value,
|
|
1034
|
+
inst,
|
|
1035
|
+
continue: !def.abort
|
|
1036
|
+
});
|
|
1037
|
+
};
|
|
1038
|
+
});
|
|
1039
|
+
const $ZodCheckNumberFormat = /*@__PURE__*/ $constructor("$ZodCheckNumberFormat", (inst, def)=>{
|
|
1040
|
+
$ZodCheck.init(inst, def);
|
|
1041
|
+
def.format = def.format || "float64";
|
|
1042
|
+
const isInt = def.format?.includes("int");
|
|
1043
|
+
const origin = isInt ? "int" : "number";
|
|
1044
|
+
const [minimum, maximum] = NUMBER_FORMAT_RANGES[def.format];
|
|
1045
|
+
inst._zod.check = (payload)=>{
|
|
1046
|
+
const input = payload.value;
|
|
1047
|
+
if (isInt) {
|
|
1048
|
+
if (!Number.isInteger(input)) return void payload.issues.push({
|
|
1049
|
+
expected: origin,
|
|
1050
|
+
format: def.format,
|
|
1051
|
+
code: "invalid_type",
|
|
1052
|
+
continue: false,
|
|
1053
|
+
input,
|
|
1054
|
+
inst
|
|
1055
|
+
});
|
|
1056
|
+
if (!Number.isSafeInteger(input)) {
|
|
1057
|
+
if (input > 0) payload.issues.push({
|
|
1058
|
+
input,
|
|
1059
|
+
code: "too_big",
|
|
1060
|
+
maximum: Number.MAX_SAFE_INTEGER,
|
|
1061
|
+
note: "Integers must be within the safe integer range.",
|
|
1062
|
+
inst,
|
|
1063
|
+
origin,
|
|
1064
|
+
inclusive: true,
|
|
1065
|
+
continue: !def.abort
|
|
1066
|
+
});
|
|
1067
|
+
else payload.issues.push({
|
|
1068
|
+
input,
|
|
1069
|
+
code: "too_small",
|
|
1070
|
+
minimum: Number.MIN_SAFE_INTEGER,
|
|
1071
|
+
note: "Integers must be within the safe integer range.",
|
|
1072
|
+
inst,
|
|
1073
|
+
origin,
|
|
1074
|
+
inclusive: true,
|
|
1075
|
+
continue: !def.abort
|
|
1076
|
+
});
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
if (input < minimum) payload.issues.push({
|
|
1081
|
+
origin: "number",
|
|
1082
|
+
input,
|
|
1083
|
+
code: "too_small",
|
|
1084
|
+
minimum,
|
|
1085
|
+
inclusive: true,
|
|
1086
|
+
inst,
|
|
1087
|
+
continue: !def.abort
|
|
1088
|
+
});
|
|
1089
|
+
if (input > maximum) payload.issues.push({
|
|
1090
|
+
origin: "number",
|
|
1091
|
+
input,
|
|
1092
|
+
code: "too_big",
|
|
1093
|
+
maximum,
|
|
1094
|
+
inclusive: true,
|
|
1095
|
+
inst,
|
|
1096
|
+
continue: !def.abort
|
|
1097
|
+
});
|
|
1098
|
+
};
|
|
1099
|
+
});
|
|
977
1100
|
const $ZodCheckMaxLength = /*@__PURE__*/ $constructor("$ZodCheckMaxLength", (inst, def)=>{
|
|
978
1101
|
var _a;
|
|
979
1102
|
$ZodCheck.init(inst, def);
|
|
@@ -1630,6 +1753,32 @@ const $ZodJWT = /*@__PURE__*/ $constructor("$ZodJWT", (inst, def)=>{
|
|
|
1630
1753
|
});
|
|
1631
1754
|
};
|
|
1632
1755
|
});
|
|
1756
|
+
const $ZodNumber = /*@__PURE__*/ $constructor("$ZodNumber", (inst, def)=>{
|
|
1757
|
+
$ZodType.init(inst, def);
|
|
1758
|
+
inst._zod.pattern = number;
|
|
1759
|
+
inst._zod.parse = (payload, _ctx)=>{
|
|
1760
|
+
if (def.coerce) try {
|
|
1761
|
+
payload.value = Number(payload.value);
|
|
1762
|
+
} catch (_) {}
|
|
1763
|
+
const input = payload.value;
|
|
1764
|
+
if ("number" == typeof input && !Number.isNaN(input) && Number.isFinite(input)) return payload;
|
|
1765
|
+
const received = "number" == typeof input ? Number.isNaN(input) ? "NaN" : Number.isFinite(input) ? void 0 : String(input) : void 0;
|
|
1766
|
+
payload.issues.push({
|
|
1767
|
+
expected: "number",
|
|
1768
|
+
code: "invalid_type",
|
|
1769
|
+
input,
|
|
1770
|
+
inst,
|
|
1771
|
+
...received ? {
|
|
1772
|
+
received
|
|
1773
|
+
} : {}
|
|
1774
|
+
});
|
|
1775
|
+
return payload;
|
|
1776
|
+
};
|
|
1777
|
+
});
|
|
1778
|
+
const $ZodNumberFormat = /*@__PURE__*/ $constructor("$ZodNumberFormat", (inst, def)=>{
|
|
1779
|
+
$ZodCheckNumberFormat.init(inst, def);
|
|
1780
|
+
$ZodNumber.init(inst, def);
|
|
1781
|
+
});
|
|
1633
1782
|
const $ZodBoolean = /*@__PURE__*/ $constructor("$ZodBoolean", (inst, def)=>{
|
|
1634
1783
|
$ZodType.init(inst, def);
|
|
1635
1784
|
inst._zod.pattern = regexes_boolean;
|
|
@@ -2731,6 +2880,22 @@ function _isoDuration(Class, params) {
|
|
|
2731
2880
|
...normalizeParams(params)
|
|
2732
2881
|
});
|
|
2733
2882
|
}
|
|
2883
|
+
function _number(Class, params) {
|
|
2884
|
+
return new Class(snapshotChecks({
|
|
2885
|
+
type: "number",
|
|
2886
|
+
checks: [],
|
|
2887
|
+
...normalizeParams(params)
|
|
2888
|
+
}));
|
|
2889
|
+
}
|
|
2890
|
+
function _int(Class, params) {
|
|
2891
|
+
return new Class({
|
|
2892
|
+
type: "number",
|
|
2893
|
+
check: "number_format",
|
|
2894
|
+
abort: false,
|
|
2895
|
+
format: "safeint",
|
|
2896
|
+
...normalizeParams(params)
|
|
2897
|
+
});
|
|
2898
|
+
}
|
|
2734
2899
|
function _boolean(Class, params) {
|
|
2735
2900
|
return new Class({
|
|
2736
2901
|
type: "boolean",
|
|
@@ -2748,6 +2913,45 @@ function _never(Class, params) {
|
|
|
2748
2913
|
...normalizeParams(params)
|
|
2749
2914
|
});
|
|
2750
2915
|
}
|
|
2916
|
+
function _lt(value, params) {
|
|
2917
|
+
return new $ZodCheckLessThan({
|
|
2918
|
+
check: "less_than",
|
|
2919
|
+
...normalizeParams(params),
|
|
2920
|
+
value,
|
|
2921
|
+
inclusive: false
|
|
2922
|
+
});
|
|
2923
|
+
}
|
|
2924
|
+
function _lte(value, params) {
|
|
2925
|
+
return new $ZodCheckLessThan({
|
|
2926
|
+
check: "less_than",
|
|
2927
|
+
...normalizeParams(params),
|
|
2928
|
+
value,
|
|
2929
|
+
inclusive: true
|
|
2930
|
+
});
|
|
2931
|
+
}
|
|
2932
|
+
function _gt(value, params) {
|
|
2933
|
+
return new $ZodCheckGreaterThan({
|
|
2934
|
+
check: "greater_than",
|
|
2935
|
+
...normalizeParams(params),
|
|
2936
|
+
value,
|
|
2937
|
+
inclusive: false
|
|
2938
|
+
});
|
|
2939
|
+
}
|
|
2940
|
+
function _gte(value, params) {
|
|
2941
|
+
return new $ZodCheckGreaterThan({
|
|
2942
|
+
check: "greater_than",
|
|
2943
|
+
...normalizeParams(params),
|
|
2944
|
+
value,
|
|
2945
|
+
inclusive: true
|
|
2946
|
+
});
|
|
2947
|
+
}
|
|
2948
|
+
function _multipleOf(value, params) {
|
|
2949
|
+
return new $ZodCheckMultipleOf({
|
|
2950
|
+
check: "multiple_of",
|
|
2951
|
+
...normalizeParams(params),
|
|
2952
|
+
value
|
|
2953
|
+
});
|
|
2954
|
+
}
|
|
2751
2955
|
function _maxLength(maximum, params) {
|
|
2752
2956
|
const ch = new $ZodCheckMaxLength({
|
|
2753
2957
|
check: "max_length",
|
|
@@ -3459,6 +3663,37 @@ const stringProcessor = (schema, ctx, _json, _params)=>{
|
|
|
3459
3663
|
];
|
|
3460
3664
|
}
|
|
3461
3665
|
};
|
|
3666
|
+
const numberProcessor = (schema, ctx, _json, params)=>{
|
|
3667
|
+
const json = _json;
|
|
3668
|
+
const { minimum, maximum, multipleOf, exclusiveMaximum, exclusiveMinimum, isInt } = aggregateChecks(schema);
|
|
3669
|
+
json.type = isInt ? "integer" : "number";
|
|
3670
|
+
const exMin = "number" == typeof exclusiveMinimum && exclusiveMinimum >= (minimum ?? -1 / 0);
|
|
3671
|
+
const exMax = "number" == typeof exclusiveMaximum && exclusiveMaximum <= (maximum ?? 1 / 0);
|
|
3672
|
+
const legacy = "draft-04" === ctx.target || "openapi-3.0" === ctx.target;
|
|
3673
|
+
if (exMin) if (legacy) {
|
|
3674
|
+
json.minimum = exclusiveMinimum;
|
|
3675
|
+
json.exclusiveMinimum = true;
|
|
3676
|
+
} else json.exclusiveMinimum = exclusiveMinimum;
|
|
3677
|
+
else if ("number" == typeof minimum) json.minimum = minimum;
|
|
3678
|
+
if (exMax) if (legacy) {
|
|
3679
|
+
json.maximum = exclusiveMaximum;
|
|
3680
|
+
json.exclusiveMaximum = true;
|
|
3681
|
+
} else json.exclusiveMaximum = exclusiveMaximum;
|
|
3682
|
+
else if ("number" == typeof maximum) json.maximum = maximum;
|
|
3683
|
+
if (multipleOf) {
|
|
3684
|
+
const divisors = new Set();
|
|
3685
|
+
for (const divisor of multipleOf)if (Number.isFinite(divisor) && 0 !== divisor) divisors.add(Math.abs(divisor));
|
|
3686
|
+
else to_json_schema_handleUnrepresentable(schema, ctx, json, params, `A multipleOf divisor of ${divisor} cannot be represented in JSON Schema`);
|
|
3687
|
+
const [first, ...rest] = divisors;
|
|
3688
|
+
if (void 0 !== first) json.multipleOf = first;
|
|
3689
|
+
if (rest.length) json.allOf = [
|
|
3690
|
+
...json.allOf ?? [],
|
|
3691
|
+
...rest.map((m)=>({
|
|
3692
|
+
multipleOf: m
|
|
3693
|
+
}))
|
|
3694
|
+
];
|
|
3695
|
+
}
|
|
3696
|
+
};
|
|
3462
3697
|
const booleanProcessor = (_schema, _ctx, json, _params)=>{
|
|
3463
3698
|
json.type = "boolean";
|
|
3464
3699
|
};
|
|
@@ -4645,6 +4880,82 @@ const ZodJWT = /*@__PURE__*/ $constructor("ZodJWT", (inst, def)=>{
|
|
|
4645
4880
|
$ZodJWT.init(inst, def);
|
|
4646
4881
|
ZodStringFormat.init(inst, def);
|
|
4647
4882
|
});
|
|
4883
|
+
const ZodNumber = /*@__PURE__*/ $constructor("ZodNumber", (inst, def)=>{
|
|
4884
|
+
$ZodNumber.init(inst, def);
|
|
4885
|
+
ZodType.init(inst, def);
|
|
4886
|
+
inst._zod.processJSONSchema = (ctx, json, params)=>numberProcessor(inst, ctx, json, params);
|
|
4887
|
+
inst.isFinite = true;
|
|
4888
|
+
}, /*@__PURE__*/ derived({
|
|
4889
|
+
minValue: (inst)=>{
|
|
4890
|
+
const { minimum, exclusiveMinimum } = aggregateChecks(inst);
|
|
4891
|
+
return Math.max(minimum ?? -1 / 0, exclusiveMinimum ?? -1 / 0);
|
|
4892
|
+
},
|
|
4893
|
+
maxValue: (inst)=>{
|
|
4894
|
+
const { maximum, exclusiveMaximum } = aggregateChecks(inst);
|
|
4895
|
+
return Math.min(maximum ?? 1 / 0, exclusiveMaximum ?? 1 / 0);
|
|
4896
|
+
},
|
|
4897
|
+
isInt: (inst)=>{
|
|
4898
|
+
const { isInt, multipleOf } = aggregateChecks(inst);
|
|
4899
|
+
return !!isInt || !!multipleOf?.some(Number.isSafeInteger);
|
|
4900
|
+
},
|
|
4901
|
+
format: (inst)=>aggregateChecks(inst).format ?? null
|
|
4902
|
+
}, {
|
|
4903
|
+
gt (value, params) {
|
|
4904
|
+
return this.check(_gt(value, params));
|
|
4905
|
+
},
|
|
4906
|
+
gte (value, params) {
|
|
4907
|
+
return this.check(_gte(value, params));
|
|
4908
|
+
},
|
|
4909
|
+
min (value, params) {
|
|
4910
|
+
return this.check(_gte(value, params));
|
|
4911
|
+
},
|
|
4912
|
+
lt (value, params) {
|
|
4913
|
+
return this.check(_lt(value, params));
|
|
4914
|
+
},
|
|
4915
|
+
lte (value, params) {
|
|
4916
|
+
return this.check(_lte(value, params));
|
|
4917
|
+
},
|
|
4918
|
+
max (value, params) {
|
|
4919
|
+
return this.check(_lte(value, params));
|
|
4920
|
+
},
|
|
4921
|
+
int (params) {
|
|
4922
|
+
return this.check(schemas_int(params));
|
|
4923
|
+
},
|
|
4924
|
+
safe (params) {
|
|
4925
|
+
return this.check(schemas_int(params));
|
|
4926
|
+
},
|
|
4927
|
+
positive (params) {
|
|
4928
|
+
return this.check(_gt(0, params));
|
|
4929
|
+
},
|
|
4930
|
+
nonnegative (params) {
|
|
4931
|
+
return this.check(_gte(0, params));
|
|
4932
|
+
},
|
|
4933
|
+
negative (params) {
|
|
4934
|
+
return this.check(_lt(0, params));
|
|
4935
|
+
},
|
|
4936
|
+
nonpositive (params) {
|
|
4937
|
+
return this.check(_lte(0, params));
|
|
4938
|
+
},
|
|
4939
|
+
multipleOf (value, params) {
|
|
4940
|
+
return this.check(_multipleOf(value, params));
|
|
4941
|
+
},
|
|
4942
|
+
step (value, params) {
|
|
4943
|
+
return this.check(_multipleOf(value, params));
|
|
4944
|
+
},
|
|
4945
|
+
finite () {
|
|
4946
|
+
return this;
|
|
4947
|
+
}
|
|
4948
|
+
}));
|
|
4949
|
+
function schemas_number(params) {
|
|
4950
|
+
return _number(ZodNumber, params);
|
|
4951
|
+
}
|
|
4952
|
+
const ZodNumberFormat = /*@__PURE__*/ $constructor("ZodNumberFormat", (inst, def)=>{
|
|
4953
|
+
$ZodNumberFormat.init(inst, def);
|
|
4954
|
+
ZodNumber.init(inst, def);
|
|
4955
|
+
});
|
|
4956
|
+
function schemas_int(params) {
|
|
4957
|
+
return _int(ZodNumberFormat, params);
|
|
4958
|
+
}
|
|
4648
4959
|
const ZodBoolean = /*@__PURE__*/ $constructor("ZodBoolean", (inst, def)=>{
|
|
4649
4960
|
$ZodBoolean.init(inst, def);
|
|
4650
4961
|
ZodType.init(inst, def);
|
|
@@ -4997,6 +5308,7 @@ function refine(fn, _params = {}) {
|
|
|
4997
5308
|
function superRefine(fn, params) {
|
|
4998
5309
|
return _superRefine(fn, params);
|
|
4999
5310
|
}
|
|
5311
|
+
const rulesraw_namespaceObject = "Every numeric utility is in px, not 4px steps: p-16 is 16px, p-4 is 4px. Translate Tailwind habits (p-4 → p-16, gap-6 → gap-24, w-64 → w-256).\n\nBefore composing a screen, call get_guideline for the pattern (app shell, list page, table, drawer, form, dialog, filters, empty state, settings, auth). Wrong → right:\n\n- p-5, gap-3, p-10 → stay on the scale: 1, 2, 4, 6, 8, 12, 16, 20, 24, 28, 32, 40, 48, 64…\n- space-y-4, margins per child → <VStack gap={16}>, <HStack gap={8}>\n- Padded page wrapper → Page > PageHeader / PageToolbar / PageContent own the 24px gutter\n- p-* on Card, DrawerBody, DialogBody, PageToolbar, Table → none; they own their padding\n- className on Text, Heading, Field, Drawer/Dialog Header/Body/Footer → wrap in your own element\n- Raw <h1>, <p>, <label> → <Heading as=\"h2\" size=\"lg\">, <Text size=\"sm\">, Field\n- <Text> default (16px) for body → size=\"sm\" (md is only an alias of sm)\n- leading-none (0px), leading-5 (5px) → leading-sm or leading-20\n- weight=\"semibold\" → weight is light|regular|medium|bold (medium = 450)\n- bg-primary, text-muted, bg-card, bg-white, text-gray-500 → bg-bg-primary, text-text-secondary, bg-bg-surface-1\n- text-error, bg-red-500 → danger tokens; categorical color → Badge color\n- dark: variant, setting data-theme → semantic tokens, useTheme().setTheme()\n- rounded-lg, rounded, rounded-[15px] → rounded-8 (controls), rounded-12 (panels)\n- shadow-* on DS components, z-* on overlays → never; they own both\n- focus:ring → focus-visible:ring-3 ring-focus-primary outline-none\n- size, DIALOG_SIZES, DRAWER_SIZES → width: Dialog 400 confirm, 560 default, 960 picker; Drawer 640 default, 700 Timeline form\n- Dialog, useToast from the root → @wallarm-org/design-system/Dialog, /Toast\n- Field invalid → error on the control + FieldError\n- Hand-built side menu → RemoteShell config={NavConfig}\n- ConfirmDialog, FilterBar, Combobox → compose Dialog parts, PageToolbar + FilterDropdown, Select + useSelectSearch\n- data-slot in tests → data-testid\n";
|
|
5000
5312
|
const propMetadataSchema = schemas_object({
|
|
5001
5313
|
name: schemas_string(),
|
|
5002
5314
|
type: schemas_string(),
|
|
@@ -5022,6 +5334,10 @@ const exampleMetadataSchema = schemas_object({
|
|
|
5022
5334
|
storyTitle: schemas_string().optional(),
|
|
5023
5335
|
tags: schemas_array(schemas_string()).optional()
|
|
5024
5336
|
});
|
|
5337
|
+
const componentDocSchema = schemas_object({
|
|
5338
|
+
name: schemas_string(),
|
|
5339
|
+
markdown: schemas_string()
|
|
5340
|
+
});
|
|
5025
5341
|
const componentMetadataSchema = schemas_object({
|
|
5026
5342
|
name: schemas_string(),
|
|
5027
5343
|
description: schemas_string().optional(),
|
|
@@ -5029,7 +5345,8 @@ const componentMetadataSchema = schemas_object({
|
|
|
5029
5345
|
props: schemas_array(propMetadataSchema),
|
|
5030
5346
|
variants: schemas_array(variantMetadataSchema),
|
|
5031
5347
|
subComponents: schemas_array(subComponentMetadataSchema),
|
|
5032
|
-
examples: schemas_array(exampleMetadataSchema)
|
|
5348
|
+
examples: schemas_array(exampleMetadataSchema),
|
|
5349
|
+
docs: schemas_array(componentDocSchema).optional()
|
|
5033
5350
|
});
|
|
5034
5351
|
const tokenMetadataSchema = schemas_object({
|
|
5035
5352
|
name: schemas_string(),
|
|
@@ -5046,6 +5363,39 @@ const designSystemMetadataSchema = schemas_object({
|
|
|
5046
5363
|
components: schemas_array(componentMetadataSchema),
|
|
5047
5364
|
tokens: schemas_array(tokenCategorySchema)
|
|
5048
5365
|
});
|
|
5366
|
+
const guidelineSectionSchema = schemas_object({
|
|
5367
|
+
id: schemas_string(),
|
|
5368
|
+
heading: schemas_string(),
|
|
5369
|
+
markdown: schemas_string()
|
|
5370
|
+
});
|
|
5371
|
+
const guidelineStorySchema = schemas_object({
|
|
5372
|
+
name: schemas_string(),
|
|
5373
|
+
code: schemas_string(),
|
|
5374
|
+
description: schemas_string().optional()
|
|
5375
|
+
});
|
|
5376
|
+
const guidelineKindSchema = schemas_enum([
|
|
5377
|
+
'foundation',
|
|
5378
|
+
'pattern'
|
|
5379
|
+
]);
|
|
5380
|
+
const guidelineSchema = schemas_object({
|
|
5381
|
+
slug: schemas_string(),
|
|
5382
|
+
kind: guidelineKindSchema,
|
|
5383
|
+
title: schemas_string(),
|
|
5384
|
+
description: schemas_string(),
|
|
5385
|
+
components: schemas_array(schemas_string()),
|
|
5386
|
+
keywords: schemas_array(schemas_string()),
|
|
5387
|
+
order: schemas_number(),
|
|
5388
|
+
markdown: schemas_string(),
|
|
5389
|
+
sections: schemas_array(guidelineSectionSchema),
|
|
5390
|
+
stories: schemas_array(guidelineStorySchema).optional()
|
|
5391
|
+
});
|
|
5392
|
+
const guidelinesMetadataSchema = schemas_object({
|
|
5393
|
+
version: schemas_string(),
|
|
5394
|
+
rules: schemas_string(),
|
|
5395
|
+
guidelines: schemas_array(guidelineSchema)
|
|
5396
|
+
});
|
|
5397
|
+
const DS_METADATA = path_0.join('@wallarm-org', 'design-system', 'dist', 'metadata', 'components.json');
|
|
5398
|
+
const describeSource = (source, version)=>'installed' === source.kind ? `Source: installed @wallarm-org/design-system v${version}` : `Source: bundled snapshot v${version} — may differ from your installed version (install @wallarm-org/design-system or pass --metadata-path)`;
|
|
5049
5399
|
function parseMetadataPathArg(args) {
|
|
5050
5400
|
const idx = args.indexOf('--metadata-path');
|
|
5051
5401
|
if (-1 !== idx && idx + 1 < args.length) return args[idx + 1];
|
|
@@ -5054,11 +5404,27 @@ function findMetadataInNodeModules(startDir) {
|
|
|
5054
5404
|
let dir = startDir;
|
|
5055
5405
|
const root = path_0.parse(dir).root;
|
|
5056
5406
|
while(dir !== root){
|
|
5057
|
-
const candidate = path_0.join(dir, 'node_modules',
|
|
5407
|
+
const candidate = path_0.join(dir, 'node_modules', DS_METADATA);
|
|
5058
5408
|
if (fs.existsSync(candidate)) return candidate;
|
|
5059
5409
|
dir = path_0.dirname(dir);
|
|
5060
5410
|
}
|
|
5061
5411
|
}
|
|
5412
|
+
function findMetadataInWorkspaces(cwd) {
|
|
5413
|
+
for (const group of [
|
|
5414
|
+
'apps',
|
|
5415
|
+
'packages'
|
|
5416
|
+
]){
|
|
5417
|
+
const groupDir = path_0.join(cwd, group);
|
|
5418
|
+
if (fs.existsSync(groupDir)) for (const entry of fs.readdirSync(groupDir, {
|
|
5419
|
+
withFileTypes: true
|
|
5420
|
+
})){
|
|
5421
|
+
if (!entry.isDirectory()) continue;
|
|
5422
|
+
const candidate = path_0.join(groupDir, entry.name, 'node_modules', DS_METADATA);
|
|
5423
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
5424
|
+
}
|
|
5425
|
+
}
|
|
5426
|
+
}
|
|
5427
|
+
const defaultBundledPath = ()=>path_0.join(path_0.dirname(fileURLToPath(import.meta.url)), 'metadata', 'components.json');
|
|
5062
5428
|
async function resolveViaImportMeta() {
|
|
5063
5429
|
try {
|
|
5064
5430
|
const resolved = import.meta.resolve('@wallarm-org/design-system/metadata');
|
|
@@ -5066,19 +5432,46 @@ async function resolveViaImportMeta() {
|
|
|
5066
5432
|
if (fs.existsSync(filePath)) return filePath;
|
|
5067
5433
|
} catch {}
|
|
5068
5434
|
}
|
|
5069
|
-
async function loadMetadata(args = process.argv) {
|
|
5435
|
+
async function loadMetadata(args = process.argv, { cwd = process.cwd(), bundledPath = defaultBundledPath(), resolveInstalled = resolveViaImportMeta } = {}) {
|
|
5070
5436
|
let metadataPath;
|
|
5437
|
+
let kind = 'installed';
|
|
5071
5438
|
metadataPath = parseMetadataPathArg(args);
|
|
5072
5439
|
if (metadataPath) metadataPath = path_0.resolve(metadataPath);
|
|
5073
|
-
if (!metadataPath) metadataPath = await
|
|
5074
|
-
if (!metadataPath) metadataPath = findMetadataInNodeModules(
|
|
5440
|
+
if (!metadataPath) metadataPath = await resolveInstalled();
|
|
5441
|
+
if (!metadataPath) metadataPath = findMetadataInNodeModules(cwd) ?? findMetadataInWorkspaces(cwd);
|
|
5442
|
+
if (!metadataPath && fs.existsSync(bundledPath)) {
|
|
5443
|
+
metadataPath = bundledPath;
|
|
5444
|
+
kind = 'bundled';
|
|
5445
|
+
}
|
|
5075
5446
|
if (!metadataPath) throw new Error("Could not find Wallarm Design System metadata.\n\nMake sure @wallarm-org/design-system is installed and built, or provide the path explicitly:\n wallarm-ds-mcp --metadata-path ./path/to/components.json\n");
|
|
5076
5447
|
if (!fs.existsSync(metadataPath)) throw new Error(`Metadata file not found at: ${metadataPath}`);
|
|
5077
5448
|
const raw = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
|
|
5078
5449
|
const result = designSystemMetadataSchema.safeParse(raw);
|
|
5079
5450
|
if (!result.success) throw new Error(`Invalid metadata format in ${metadataPath}:\n${result.error.message}\n\nThis might mean the design system version is incompatible with this MCP server version.`);
|
|
5080
|
-
return
|
|
5451
|
+
return {
|
|
5452
|
+
metadata: result.data,
|
|
5453
|
+
metadataPath,
|
|
5454
|
+
source: {
|
|
5455
|
+
kind,
|
|
5456
|
+
path: metadataPath
|
|
5457
|
+
}
|
|
5458
|
+
};
|
|
5459
|
+
}
|
|
5460
|
+
function loadGuidelines(metadataPath, warn = (message)=>console.error('[wallarm-ds-mcp]', message)) {
|
|
5461
|
+
const guidelinesPath = path_0.join(path_0.dirname(metadataPath), 'guidelines.json');
|
|
5462
|
+
if (!fs.existsSync(guidelinesPath)) return;
|
|
5463
|
+
try {
|
|
5464
|
+
const raw = JSON.parse(fs.readFileSync(guidelinesPath, 'utf-8'));
|
|
5465
|
+
if (raw && Array.isArray(raw.guidelines)) raw.guidelines = raw.guidelines.filter((g)=>guidelineKindSchema.safeParse(g?.kind).success);
|
|
5466
|
+
const result = guidelinesMetadataSchema.safeParse(raw);
|
|
5467
|
+
if (!result.success) throw new Error(result.error.message);
|
|
5468
|
+
return result.data;
|
|
5469
|
+
} catch (err) {
|
|
5470
|
+
warn(`Ignoring ${guidelinesPath} (guidelines unavailable): ${err instanceof Error ? err.message : String(err)}`);
|
|
5471
|
+
return;
|
|
5472
|
+
}
|
|
5081
5473
|
}
|
|
5474
|
+
const loadBundledGuidelines = (bundledPath = defaultBundledPath())=>loadGuidelines(bundledPath);
|
|
5082
5475
|
function getComponentListContent(metadata) {
|
|
5083
5476
|
const lines = [
|
|
5084
5477
|
`# Wallarm Design System Components (v${metadata.version})`,
|
|
@@ -5110,105 +5503,467 @@ function getTokensContent(metadata) {
|
|
|
5110
5503
|
}
|
|
5111
5504
|
return lines.join('\n');
|
|
5112
5505
|
}
|
|
5506
|
+
const RESPONSE_LIMIT = 40000;
|
|
5507
|
+
const splitUnion = (type)=>{
|
|
5508
|
+
const parts = [];
|
|
5509
|
+
let depth = 0;
|
|
5510
|
+
let quote = '';
|
|
5511
|
+
let start = 0;
|
|
5512
|
+
for(let i = 0; i < type.length; i++){
|
|
5513
|
+
const ch = type[i];
|
|
5514
|
+
if (quote) {
|
|
5515
|
+
if (ch === quote) quote = '';
|
|
5516
|
+
} else if ('"' === ch || "'" === ch || '`' === ch) quote = ch;
|
|
5517
|
+
else if ('{(<['.includes(ch ?? '')) depth++;
|
|
5518
|
+
else if ('})>]'.includes(ch ?? '') && !('>' === ch && '=' === type[i - 1])) depth--;
|
|
5519
|
+
else if ('|' === ch && 0 === depth) {
|
|
5520
|
+
parts.push(type.slice(start, i).trim());
|
|
5521
|
+
start = i + 1;
|
|
5522
|
+
}
|
|
5523
|
+
}
|
|
5524
|
+
parts.push(type.slice(start).trim());
|
|
5525
|
+
return parts.filter(Boolean);
|
|
5526
|
+
};
|
|
5527
|
+
const formatType = (type, maxMembers = 8, maxLength = 160)=>{
|
|
5528
|
+
const members = splitUnion(type).filter((m)=>'undefined' !== m);
|
|
5529
|
+
let out = members.length > maxMembers ? `${members.slice(0, maxMembers).join(' | ')} | …(+${members.length - maxMembers})` : members.join(' | ') || type;
|
|
5530
|
+
if (out.length > maxLength) out = `${out.slice(0, maxLength)}…`;
|
|
5531
|
+
return out;
|
|
5532
|
+
};
|
|
5533
|
+
const cell = (text)=>text.replace(/\s*\n\s*/g, ' ').replace(/\|/g, '\\|');
|
|
5534
|
+
const oneLine = (text, max = 140)=>{
|
|
5535
|
+
const flat = text.replace(/\s*\n\s*/g, ' ').trim();
|
|
5536
|
+
const end = flat.search(/[.!?](\s|$)/);
|
|
5537
|
+
const sentence = -1 === end ? flat : flat.slice(0, end + 1);
|
|
5538
|
+
return sentence.length > max ? `${sentence.slice(0, max)}…` : sentence;
|
|
5539
|
+
};
|
|
5540
|
+
const formatPropsTable = (props, options = {})=>{
|
|
5541
|
+
if (0 === props.length) return '';
|
|
5542
|
+
const { brief = false, maxMembers = brief ? 8 : 30 } = options;
|
|
5543
|
+
const hasRequired = props.some((p)=>p.required);
|
|
5544
|
+
const hasDefault = props.some((p)=>void 0 !== p.defaultValue);
|
|
5545
|
+
const hasDescription = props.some((p)=>p.description);
|
|
5546
|
+
const head = [
|
|
5547
|
+
'Prop',
|
|
5548
|
+
'Type'
|
|
5549
|
+
];
|
|
5550
|
+
if (hasRequired) head.push('Required');
|
|
5551
|
+
if (hasDefault) head.push('Default');
|
|
5552
|
+
if (hasDescription) head.push("Description");
|
|
5553
|
+
const rows = props.map((p)=>{
|
|
5554
|
+
const row = [
|
|
5555
|
+
`\`${p.name}\``,
|
|
5556
|
+
`\`${cell(formatType(p.type, maxMembers, brief ? 160 : 400))}\``
|
|
5557
|
+
];
|
|
5558
|
+
if (hasRequired) row.push(p.required ? 'yes' : '');
|
|
5559
|
+
if (hasDefault) row.push(void 0 !== p.defaultValue ? `\`${cell(p.defaultValue)}\`` : '');
|
|
5560
|
+
if (hasDescription) {
|
|
5561
|
+
const desc = p.description ?? '';
|
|
5562
|
+
row.push(cell(brief ? oneLine(desc) : desc));
|
|
5563
|
+
}
|
|
5564
|
+
return `| ${row.join(' | ')} |`;
|
|
5565
|
+
});
|
|
5566
|
+
return [
|
|
5567
|
+
`| ${head.join(' | ')} |`,
|
|
5568
|
+
`|${head.map(()=>'---').join('|')}|`,
|
|
5569
|
+
...rows
|
|
5570
|
+
].join('\n');
|
|
5571
|
+
};
|
|
5572
|
+
const paginate = (text, page, nextCall, limit = RESPONSE_LIMIT)=>{
|
|
5573
|
+
const budget = limit - 1000;
|
|
5574
|
+
if (text.length <= budget && page <= 1) return text;
|
|
5575
|
+
const blocks = text.split(/\n(?=#{1,3} )/);
|
|
5576
|
+
const pages = [];
|
|
5577
|
+
let current = '';
|
|
5578
|
+
const push = (piece)=>{
|
|
5579
|
+
if (current && current.length + piece.length + 1 > budget) {
|
|
5580
|
+
pages.push(current);
|
|
5581
|
+
current = '';
|
|
5582
|
+
}
|
|
5583
|
+
current = current ? `${current}\n${piece}` : piece;
|
|
5584
|
+
};
|
|
5585
|
+
for (const block of blocks)if (block.length <= budget) push(block);
|
|
5586
|
+
else for (const line of block.split('\n'))push(line.slice(0, budget));
|
|
5587
|
+
if (current) pages.push(current);
|
|
5588
|
+
const total = pages.length;
|
|
5589
|
+
const index = Math.min(Math.max(page, 1), total) - 1;
|
|
5590
|
+
const footer = index + 1 < total ? `\n\n---\nPage ${index + 1}/${total}. Call again with ${nextCall(index + 2)} for the next page.` : `\n\n---\nPage ${index + 1}/${total} (last).`;
|
|
5591
|
+
return `${pages[index]}${footer}`;
|
|
5592
|
+
};
|
|
5113
5593
|
function getComponent(metadata, name) {
|
|
5114
5594
|
return metadata.components.find((c)=>c.name.toLowerCase() === name.toLowerCase());
|
|
5115
5595
|
}
|
|
5116
|
-
function
|
|
5117
|
-
const
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
lines.push('|------|------|----------|---------|-------------|');
|
|
5125
|
-
for (const prop of component.props){
|
|
5126
|
-
const req = prop.required ? 'Yes' : 'No';
|
|
5127
|
-
const def = prop.defaultValue ?? '-';
|
|
5128
|
-
const desc = prop.description ?? '-';
|
|
5129
|
-
lines.push(`| \`${prop.name}\` | \`${prop.type}\` | ${req} | \`${def}\` | ${desc} |`);
|
|
5130
|
-
}
|
|
5596
|
+
function findSubComponentOwner(metadata, name) {
|
|
5597
|
+
const n = name.toLowerCase();
|
|
5598
|
+
for (const component of metadata.components){
|
|
5599
|
+
const sub = component.subComponents.find((s)=>s.name.toLowerCase() === n);
|
|
5600
|
+
if (sub) return {
|
|
5601
|
+
component,
|
|
5602
|
+
sub
|
|
5603
|
+
};
|
|
5131
5604
|
}
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5605
|
+
}
|
|
5606
|
+
const storyName = (example)=>example.storyTitle?.split('/').at(-1) ?? 'Story';
|
|
5607
|
+
function exampleEntries(component) {
|
|
5608
|
+
const counts = new Map();
|
|
5609
|
+
for (const e of component.examples)counts.set(e.name, (counts.get(e.name) ?? 0) + 1);
|
|
5610
|
+
return component.examples.map((example)=>({
|
|
5611
|
+
key: (counts.get(example.name) ?? 0) > 1 ? `${storyName(example)}/${example.name}` : example.name,
|
|
5612
|
+
example
|
|
5613
|
+
}));
|
|
5614
|
+
}
|
|
5615
|
+
function sharedProps(subs) {
|
|
5616
|
+
const counts = new Map();
|
|
5617
|
+
for (const sub of subs)for (const p of sub.props)if (!p.required && !p.description) counts.set(p.name, (counts.get(p.name) ?? 0) + 1);
|
|
5618
|
+
const min = Math.max(3, Math.ceil(subs.length / 2));
|
|
5619
|
+
return new Set([
|
|
5620
|
+
...counts
|
|
5621
|
+
].filter(([, n])=>n >= min).map(([name])=>name));
|
|
5622
|
+
}
|
|
5623
|
+
const withoutShared = (props, shared)=>props.filter((p)=>p.required || p.description || !shared.has(p.name));
|
|
5624
|
+
const sharedNote = (shared)=>shared.size > 0 ? `_Not repeated per sub-component — most also accept: ${[
|
|
5625
|
+
...shared
|
|
5626
|
+
].map((n)=>`\`${n}\``).join(', ')}._` : '';
|
|
5627
|
+
const call = (args)=>`\`{ ${Object.entries(args).map(([k, v])=>`${k}: ${'number' == typeof v ? v : `'${v}'`}`).join(', ')} }\``;
|
|
5628
|
+
const formatVariantList = (c)=>c.variants.map((v)=>{
|
|
5629
|
+
const def = v.defaultValue ? ` (default: \`${v.defaultValue}\`)` : '';
|
|
5630
|
+
const options = v.options.length > 0 ? v.options.map((o)=>`\`${o}\``).join(', ') : '_dynamically generated_';
|
|
5631
|
+
return `- **${v.name}**: ${options}${def}`;
|
|
5632
|
+
}).join('\n');
|
|
5633
|
+
function formatOverview(c) {
|
|
5634
|
+
const lines = [
|
|
5635
|
+
`# ${c.name}`
|
|
5636
|
+
];
|
|
5637
|
+
if (c.description) lines.push('', c.description);
|
|
5638
|
+
const exported = 0 === c.props.length && c.subComponents.length > 0 ? `${c.subComponents.slice(0, 4).map((x)=>x.name).join(', ')}${c.subComponents.length > 4 ? ', …' : ''}` : c.name;
|
|
5639
|
+
lines.push('', `**Import:** \`import { ${exported} } from '${c.importPath}'\``);
|
|
5640
|
+
if (c.props.length > 0) lines.push('', '## Props', '', formatPropsTable(c.props, {
|
|
5641
|
+
brief: true
|
|
5642
|
+
}));
|
|
5643
|
+
if (c.variants.length > 0) lines.push('', '## Variants', '', formatVariantList(c));
|
|
5644
|
+
if (c.subComponents.length > 0) {
|
|
5645
|
+
lines.push('', `## Sub-components (${c.subComponents.length})`, '');
|
|
5646
|
+
if (c.subComponents.some((s)=>s.description)) for (const s of c.subComponents)lines.push(`- **${s.name}**${s.description ? ` — ${oneLine(s.description)}` : ''}`);
|
|
5647
|
+
else lines.push(c.subComponents.map((s)=>s.name).join(', '));
|
|
5648
|
+
lines.push('', `Props of one: \`get_component\` ${call({
|
|
5649
|
+
name: c.name,
|
|
5650
|
+
section: c.subComponents[0]?.name ?? ''
|
|
5651
|
+
})}; all: section \`'subcomponents'\`.`);
|
|
5139
5652
|
}
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
for (const prop of sub.props){
|
|
5149
|
-
const req = prop.required ? 'Yes' : 'No';
|
|
5150
|
-
const def = prop.defaultValue ?? '-';
|
|
5151
|
-
const desc = prop.description ?? '-';
|
|
5152
|
-
lines.push(`| \`${prop.name}\` | \`${prop.type}\` | ${req} | \`${def}\` | ${desc} |`);
|
|
5153
|
-
}
|
|
5154
|
-
}
|
|
5155
|
-
lines.push('');
|
|
5156
|
-
}
|
|
5653
|
+
const entries = exampleEntries(c);
|
|
5654
|
+
if (entries.length > 0) {
|
|
5655
|
+
lines.push('', `## Examples (${entries.length})`, '');
|
|
5656
|
+
for (const { key, example } of entries)lines.push(`- \`${key}\`${example.description ? ` — ${oneLine(example.description, 120)}` : ''}`);
|
|
5657
|
+
lines.push('', `Code: \`get_example\` ${call({
|
|
5658
|
+
component: c.name,
|
|
5659
|
+
example: entries[0]?.key ?? ''
|
|
5660
|
+
})}; all code: section \`'examples'\`.`);
|
|
5157
5661
|
}
|
|
5158
|
-
if (
|
|
5159
|
-
lines.push('', '## Usage
|
|
5160
|
-
for (const
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
}
|
|
5662
|
+
if (c.docs && c.docs.length > 0) {
|
|
5663
|
+
lines.push('', '## Usage docs', '');
|
|
5664
|
+
for (const d of c.docs)lines.push(`- \`docs/${d.name}\` (${Math.round(d.markdown.length / 1000)}k chars)`);
|
|
5665
|
+
lines.push('', `Read: \`get_component\` ${call({
|
|
5666
|
+
name: c.name,
|
|
5667
|
+
section: `docs/${c.docs[0]?.name}`
|
|
5668
|
+
})}.`);
|
|
5165
5669
|
}
|
|
5166
5670
|
return lines.join('\n');
|
|
5167
5671
|
}
|
|
5168
|
-
function
|
|
5169
|
-
|
|
5672
|
+
function formatSubComponent(c, sub) {
|
|
5673
|
+
const shared = sharedProps(c.subComponents);
|
|
5674
|
+
const lines = [
|
|
5675
|
+
`# ${c.name} › ${sub.name}`
|
|
5676
|
+
];
|
|
5677
|
+
if (sub.description) lines.push('', sub.description);
|
|
5678
|
+
lines.push('', `**Import:** \`import { ${sub.name} } from '${c.importPath}'\``);
|
|
5679
|
+
const props = withoutShared(sub.props, shared);
|
|
5680
|
+
lines.push('', props.length > 0 ? formatPropsTable(props) : '_No own props._');
|
|
5681
|
+
const note = sharedNote(shared);
|
|
5682
|
+
if (note) lines.push('', note);
|
|
5683
|
+
const usedIn = exampleEntries(c).filter(({ example })=>new RegExp(`<${sub.name}\\b`).test(example.code)).map(({ key })=>`\`${key}\``);
|
|
5684
|
+
if (usedIn.length > 0) lines.push('', `Used in examples (\`get_example\`): ${usedIn.slice(0, 10).join(', ')}${usedIn.length > 10 ? ', …' : ''}`);
|
|
5685
|
+
return lines.join('\n');
|
|
5170
5686
|
}
|
|
5171
|
-
function
|
|
5172
|
-
const
|
|
5173
|
-
lines
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
lines.push('
|
|
5178
|
-
for (const
|
|
5687
|
+
function formatSubComponents(c) {
|
|
5688
|
+
const shared = sharedProps(c.subComponents);
|
|
5689
|
+
const lines = [
|
|
5690
|
+
`# ${c.name} — sub-components`
|
|
5691
|
+
];
|
|
5692
|
+
const note = sharedNote(shared);
|
|
5693
|
+
if (note) lines.push('', note);
|
|
5694
|
+
for (const sub of c.subComponents){
|
|
5695
|
+
const props = withoutShared(sub.props, shared);
|
|
5696
|
+
lines.push('', `### ${sub.name}`);
|
|
5697
|
+
if (sub.description) lines.push('', sub.description);
|
|
5698
|
+
lines.push('', props.length > 0 ? formatPropsTable(props) : '_No own props._');
|
|
5699
|
+
}
|
|
5179
5700
|
return lines.join('\n');
|
|
5180
5701
|
}
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5702
|
+
const formatExample = (key, example)=>[
|
|
5703
|
+
`### ${key}`,
|
|
5704
|
+
...example.description ? [
|
|
5705
|
+
'',
|
|
5706
|
+
example.description
|
|
5707
|
+
] : [],
|
|
5708
|
+
'',
|
|
5709
|
+
'```tsx',
|
|
5710
|
+
example.code,
|
|
5711
|
+
'```'
|
|
5712
|
+
].join('\n');
|
|
5713
|
+
function formatExamples(c) {
|
|
5714
|
+
return [
|
|
5715
|
+
`# ${c.name} — examples`,
|
|
5716
|
+
...exampleEntries(c).map(({ key, example })=>`\n${formatExample(key, example)}`)
|
|
5717
|
+
].join('\n');
|
|
5718
|
+
}
|
|
5719
|
+
function formatProps(c) {
|
|
5720
|
+
return [
|
|
5721
|
+
`# ${c.name} — props`,
|
|
5722
|
+
'',
|
|
5723
|
+
c.props.length > 0 ? formatPropsTable(c.props) : '_No props._',
|
|
5724
|
+
...c.variants.length > 0 ? [
|
|
5725
|
+
'',
|
|
5726
|
+
'## Variants',
|
|
5727
|
+
'',
|
|
5728
|
+
formatVariantList(c)
|
|
5729
|
+
] : []
|
|
5730
|
+
].join('\n');
|
|
5731
|
+
}
|
|
5732
|
+
function componentSections(c) {
|
|
5733
|
+
return [
|
|
5734
|
+
'props',
|
|
5735
|
+
...c.subComponents.length > 0 ? [
|
|
5736
|
+
'subcomponents'
|
|
5737
|
+
] : [],
|
|
5738
|
+
...c.examples.length > 0 ? [
|
|
5739
|
+
'examples'
|
|
5740
|
+
] : [],
|
|
5741
|
+
...(c.docs ?? []).map((d)=>`docs/${d.name}`),
|
|
5742
|
+
...c.subComponents.map((s)=>s.name)
|
|
5743
|
+
];
|
|
5195
5744
|
}
|
|
5196
|
-
function
|
|
5197
|
-
|
|
5745
|
+
function formatComponent(c, options = {}) {
|
|
5746
|
+
const { section, page = 1, related = '', source } = options;
|
|
5747
|
+
const s = section?.trim().toLowerCase();
|
|
5748
|
+
let body;
|
|
5749
|
+
if (s && 'overview' !== s) if ('props' === s || 'variants' === s) body = formatProps(c);
|
|
5750
|
+
else if ('subcomponents' === s || 'sub-components' === s) body = formatSubComponents(c);
|
|
5751
|
+
else if ('examples' === s) body = formatExamples(c);
|
|
5752
|
+
else if (s.startsWith('docs/')) {
|
|
5753
|
+
const doc = c.docs?.find((d)=>d.name.toLowerCase() === s.slice(5));
|
|
5754
|
+
if (doc) body = doc.markdown;
|
|
5755
|
+
} else {
|
|
5756
|
+
const sub = c.subComponents.find((x)=>x.name.toLowerCase() === s);
|
|
5757
|
+
if (sub) body = formatSubComponent(c, sub);
|
|
5758
|
+
}
|
|
5759
|
+
else body = formatOverview(c) + related;
|
|
5760
|
+
if (void 0 === body) return `Section "${section}" not found in ${c.name}.\n\nAvailable sections: ${componentSections(c).join(', ')}`;
|
|
5761
|
+
const text = paginate(body, page, (next)=>call({
|
|
5198
5762
|
name: c.name,
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5763
|
+
...section && {
|
|
5764
|
+
section
|
|
5765
|
+
},
|
|
5766
|
+
page: next
|
|
5767
|
+
}));
|
|
5768
|
+
const footer = s ? '' : `\n\n---\nMore: \`get_component\` with section ${componentSections(c).slice(0, 4).map((x)=>`\`'${x}'\``).join(', ')}${c.subComponents.length > 0 ? ' or a sub-component name' : ''}.`;
|
|
5769
|
+
return `${text}${footer}${source ? `\n\n_${source}_` : ''}`;
|
|
5770
|
+
}
|
|
5771
|
+
const ROUTING = "Tools: `search_component` / `get_component` for components — `get_component` returns an overview, pass `section` for full props, sub-components or examples, and `get_example` for one example's code; `search_token` / `get_token_category` for design tokens; `list_guidelines` / `get_guideline` for foundations (spacing, color, typography, …) and patterns (screens and flows). Read the matching pattern with `get_guideline` before composing a screen.";
|
|
5772
|
+
function buildInstructions(rules, source) {
|
|
5773
|
+
return `${rules.trim()}\n\n${ROUTING}${source ? `\n\n${source}.` : ''}`;
|
|
5203
5774
|
}
|
|
5204
|
-
function
|
|
5775
|
+
function listGuidelines(guidelines, kind) {
|
|
5776
|
+
return guidelines.guidelines.filter((g)=>!kind || g.kind === kind);
|
|
5777
|
+
}
|
|
5778
|
+
function formatGuidelineList(list) {
|
|
5779
|
+
if (0 === list.length) return 'No guidelines found.';
|
|
5780
|
+
return list.map((g)=>{
|
|
5781
|
+
const components = g.components.length > 0 ? `\n Components: ${g.components.join(', ')}` : '';
|
|
5782
|
+
return `- **${g.slug}** — ${g.title}: ${g.description}${components}`;
|
|
5783
|
+
}).join('\n');
|
|
5784
|
+
}
|
|
5785
|
+
const splitLink = (link)=>{
|
|
5786
|
+
const [path = '', anchor] = link.trim().split('#');
|
|
5787
|
+
return {
|
|
5788
|
+
path,
|
|
5789
|
+
anchor: anchor || void 0
|
|
5790
|
+
};
|
|
5791
|
+
};
|
|
5792
|
+
const normalizeSlug = (slug)=>splitLink(slug).path.toLowerCase().replace(/^(\.{1,2}\/)+/, '').replace(/\.md$/, '').replace(/^\/+|\/+$/g, '');
|
|
5793
|
+
const baseName = (slug)=>slug.slice(slug.lastIndexOf('/') + 1);
|
|
5794
|
+
function findGuideline(guidelines, slug) {
|
|
5795
|
+
const s = normalizeSlug(slug);
|
|
5796
|
+
return guidelines.guidelines.find((g)=>g.slug === s) ?? guidelines.guidelines.find((g)=>baseName(g.slug) === s);
|
|
5797
|
+
}
|
|
5798
|
+
const levenshtein = (a, b)=>{
|
|
5799
|
+
let prev = Array.from({
|
|
5800
|
+
length: b.length + 1
|
|
5801
|
+
}, (_, i)=>i);
|
|
5802
|
+
for(let i = 1; i <= a.length; i++){
|
|
5803
|
+
const row = [
|
|
5804
|
+
i
|
|
5805
|
+
];
|
|
5806
|
+
for(let j = 1; j <= b.length; j++){
|
|
5807
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
5808
|
+
row[j] = Math.min((prev[j] ?? 0) + 1, (row[j - 1] ?? 0) + 1, (prev[j - 1] ?? 0) + cost);
|
|
5809
|
+
}
|
|
5810
|
+
prev = row;
|
|
5811
|
+
}
|
|
5812
|
+
return prev[b.length] ?? 0;
|
|
5813
|
+
};
|
|
5814
|
+
function nearestSlugs(guidelines, slug, limit = 3) {
|
|
5815
|
+
const s = baseName(normalizeSlug(slug));
|
|
5816
|
+
return guidelines.guidelines.map((g)=>{
|
|
5817
|
+
const keywordHit = g.keywords.some((k)=>k.toLowerCase().includes(s) || s.includes(k.toLowerCase()));
|
|
5818
|
+
return {
|
|
5819
|
+
slug: g.slug,
|
|
5820
|
+
distance: levenshtein(s, baseName(g.slug)) - (keywordHit ? 100 : 0)
|
|
5821
|
+
};
|
|
5822
|
+
}).sort((a, b)=>a.distance - b.distance).slice(0, limit).map((r)=>r.slug);
|
|
5823
|
+
}
|
|
5824
|
+
const STORIES = 'stories';
|
|
5825
|
+
const storiesFooter = (g)=>g.stories?.length ? `\n\n---\nLive stories (Storybook \`Patterns/${g.title}\`): ${g.stories.map((s)=>s.name).join(', ')}. Full, runnable code: \`get_guideline({ slug: '${g.slug}', section: '${STORIES}' })\`.` : '';
|
|
5826
|
+
const storiesText = (g, page)=>{
|
|
5827
|
+
const body = (g.stories ?? []).map((s)=>[
|
|
5828
|
+
`## ${s.name}`,
|
|
5829
|
+
s.description ?? '',
|
|
5830
|
+
`\`\`\`tsx\n${s.code}\n\`\`\``
|
|
5831
|
+
].filter(Boolean).join('\n\n')).join('\n\n');
|
|
5832
|
+
return paginate(`# ${g.title} — Stories\n\n${body}`, page, (next)=>`\`{ slug: '${g.slug}', section: '${STORIES}', page: ${next} }\``);
|
|
5833
|
+
};
|
|
5834
|
+
function getGuidelineText(guidelines, slug, section, page = 1) {
|
|
5835
|
+
const guideline = findGuideline(guidelines, slug);
|
|
5836
|
+
if (!guideline) {
|
|
5837
|
+
const hint = nearestSlugs(guidelines, slug);
|
|
5838
|
+
return `Guideline "${slug}" not found.${hint.length > 0 ? `\n\nDid you mean: ${hint.join(', ')}?` : ''}`;
|
|
5839
|
+
}
|
|
5840
|
+
section ??= splitLink(slug).anchor;
|
|
5841
|
+
const pageCall = (next)=>`\`{ slug: '${guideline.slug}'${section ? `, section: '${section}'` : ''}, page: ${next} }\``;
|
|
5842
|
+
if (!section) return `${paginate(guideline.markdown, page, pageCall)}${storiesFooter(guideline)}`;
|
|
5843
|
+
const id = section.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
5844
|
+
if (id === STORIES && guideline.stories?.length) return storiesText(guideline, page);
|
|
5845
|
+
const match = guideline.sections.find((s)=>s.id === id);
|
|
5846
|
+
if (!match) {
|
|
5847
|
+
const ids = [
|
|
5848
|
+
...guideline.sections.map((s)=>s.id),
|
|
5849
|
+
...guideline.stories?.length ? [
|
|
5850
|
+
STORIES
|
|
5851
|
+
] : []
|
|
5852
|
+
];
|
|
5853
|
+
return `Section "${section}" not found in ${guideline.slug}.\n\nAvailable sections: ${ids.join(', ')}`;
|
|
5854
|
+
}
|
|
5855
|
+
return paginate(`# ${guideline.title} — ${match.heading}\n\n${match.markdown}`, page, pageCall);
|
|
5856
|
+
}
|
|
5857
|
+
function relatedGuidelines(guidelines, componentName) {
|
|
5858
|
+
const name = componentName.toLowerCase();
|
|
5859
|
+
return guidelines.guidelines.filter((g)=>g.components.some((c)=>c.toLowerCase() === name));
|
|
5860
|
+
}
|
|
5861
|
+
function formatRelatedGuidelines(related) {
|
|
5862
|
+
if (0 === related.length) return '';
|
|
5863
|
+
return `\n\nRelated guidelines (read with \`get_guideline\`): ${related.map((g)=>`${g.slug} (${g.title})`).join(', ')}`;
|
|
5864
|
+
}
|
|
5865
|
+
const normalize = (s)=>s.toLowerCase().replace(/[^a-z0-9/]/g, '');
|
|
5866
|
+
function getExampleText(c, example, page = 1) {
|
|
5867
|
+
const entries = exampleEntries(c);
|
|
5868
|
+
if (0 === entries.length) return `${c.name} has no examples.`;
|
|
5869
|
+
const q = normalize(example);
|
|
5870
|
+
const byName = entries.filter((e)=>normalize(e.example.name) === q);
|
|
5871
|
+
const candidates = [
|
|
5872
|
+
entries.filter((e)=>normalize(e.key) === q),
|
|
5873
|
+
byName,
|
|
5874
|
+
entries.filter((e)=>normalize(e.key).includes(q))
|
|
5875
|
+
].find((list)=>list.length > 0) ?? [];
|
|
5876
|
+
const keys = entries.map((e)=>`\`${e.key}\``).join(', ');
|
|
5877
|
+
if (candidates.length > 1) return `"${example}" matches several ${c.name} examples: ${candidates.map((e)=>`\`${e.key}\``).join(', ')}. Pass one of them.`;
|
|
5878
|
+
const fuzzy = candidates[0] || q.length < 4 ? void 0 : entries.map((e)=>{
|
|
5879
|
+
const key = normalize(e.key);
|
|
5880
|
+
return {
|
|
5881
|
+
e,
|
|
5882
|
+
d: Math.min(levenshtein(q, key), levenshtein(q, key.split('/').at(-1) ?? ''))
|
|
5883
|
+
};
|
|
5884
|
+
}).filter(({ d })=>d <= Math.max(2, Math.floor(q.length / 4))).sort((a, b)=>a.d - b.d)[0]?.e;
|
|
5885
|
+
const match = candidates[0] ?? fuzzy;
|
|
5886
|
+
if (!match) return `Example "${example}" not found in ${c.name}.\n\nAvailable examples: ${keys}`;
|
|
5887
|
+
const closest = fuzzy ? `Closest match for "${example}": \`${fuzzy.key}\`.\n\n` : '';
|
|
5888
|
+
return closest + paginate(formatExample(match.key, match.example), page, (next)=>call({
|
|
5889
|
+
component: c.name,
|
|
5890
|
+
example: match.key,
|
|
5891
|
+
page: next
|
|
5892
|
+
}));
|
|
5893
|
+
}
|
|
5894
|
+
const COLOR_FAMILY_UTILITIES = {
|
|
5895
|
+
text: [
|
|
5896
|
+
'text'
|
|
5897
|
+
],
|
|
5898
|
+
bg: [
|
|
5899
|
+
'bg'
|
|
5900
|
+
],
|
|
5901
|
+
border: [
|
|
5902
|
+
'border'
|
|
5903
|
+
],
|
|
5904
|
+
icon: [
|
|
5905
|
+
'text'
|
|
5906
|
+
],
|
|
5907
|
+
states: [
|
|
5908
|
+
'bg'
|
|
5909
|
+
],
|
|
5910
|
+
focus: [
|
|
5911
|
+
'ring'
|
|
5912
|
+
],
|
|
5913
|
+
syntax: [
|
|
5914
|
+
'text'
|
|
5915
|
+
]
|
|
5916
|
+
};
|
|
5917
|
+
function tokenUtilities(tokenName) {
|
|
5918
|
+
const name = tokenName.replace(/^--/, '');
|
|
5919
|
+
const [, namespace = '', key = ''] = name.match(/^(color|spacing|radius|shadow|blur|icon|text|leading|tracking|font-weight|font)-(.+)$/) ?? [];
|
|
5920
|
+
switch(namespace){
|
|
5921
|
+
case 'color':
|
|
5922
|
+
{
|
|
5923
|
+
const family = key.split('-')[0] ?? '';
|
|
5924
|
+
return (COLOR_FAMILY_UTILITIES[family] ?? [
|
|
5925
|
+
'bg',
|
|
5926
|
+
'text',
|
|
5927
|
+
'border'
|
|
5928
|
+
]).map((u)=>`${u}-${key}`);
|
|
5929
|
+
}
|
|
5930
|
+
case 'spacing':
|
|
5931
|
+
return [
|
|
5932
|
+
'p',
|
|
5933
|
+
'm',
|
|
5934
|
+
'gap',
|
|
5935
|
+
'w',
|
|
5936
|
+
'h'
|
|
5937
|
+
].map((u)=>`${u}-${key}`);
|
|
5938
|
+
case 'radius':
|
|
5939
|
+
return [
|
|
5940
|
+
`rounded-${key}`
|
|
5941
|
+
];
|
|
5942
|
+
case 'text':
|
|
5943
|
+
return key.endsWith('--line-height') ? [] : [
|
|
5944
|
+
`text-${key}`
|
|
5945
|
+
];
|
|
5946
|
+
case 'font-weight':
|
|
5947
|
+
case 'font':
|
|
5948
|
+
return [
|
|
5949
|
+
`font-${key}`
|
|
5950
|
+
];
|
|
5951
|
+
case '':
|
|
5952
|
+
return [];
|
|
5953
|
+
default:
|
|
5954
|
+
return [
|
|
5955
|
+
`${namespace}-${key}`
|
|
5956
|
+
];
|
|
5957
|
+
}
|
|
5958
|
+
}
|
|
5959
|
+
function scoreToken(token, utilities, category, query) {
|
|
5205
5960
|
const q = query.toLowerCase();
|
|
5206
5961
|
const name = token.name.toLowerCase();
|
|
5207
5962
|
const val = token.value.toLowerCase();
|
|
5208
5963
|
const cat = category.toLowerCase();
|
|
5209
|
-
if (name === q || name === `--${q}`) return 100;
|
|
5964
|
+
if (name === q || name === `--${q}` || utilities.includes(q)) return 100;
|
|
5210
5965
|
if (name.startsWith(q) || name.startsWith(`--${q}`)) return 80;
|
|
5211
|
-
if (name.includes(q)) return 60;
|
|
5966
|
+
if (name.includes(q) || utilities.some((u)=>u.includes(q))) return 60;
|
|
5212
5967
|
if (val === q) return 50;
|
|
5213
5968
|
if (val.includes(q)) return 40;
|
|
5214
5969
|
if (cat === q) return 30;
|
|
@@ -5218,29 +5973,344 @@ function scoreToken(token, category, query) {
|
|
|
5218
5973
|
function searchTokens(metadata, query) {
|
|
5219
5974
|
const results = [];
|
|
5220
5975
|
for (const category of metadata.tokens)for (const token of category.tokens){
|
|
5221
|
-
const
|
|
5976
|
+
const utilities = tokenUtilities(token.name);
|
|
5977
|
+
const score = scoreToken(token, utilities, category.name, query);
|
|
5222
5978
|
if (score > 0) results.push({
|
|
5223
5979
|
name: token.name,
|
|
5224
5980
|
value: token.value,
|
|
5225
5981
|
category: category.name,
|
|
5982
|
+
utilities,
|
|
5226
5983
|
score
|
|
5227
5984
|
});
|
|
5228
5985
|
}
|
|
5229
5986
|
return results.sort((a, b)=>b.score - a.score);
|
|
5230
5987
|
}
|
|
5988
|
+
function getTokenCategory(metadata, name) {
|
|
5989
|
+
return metadata.tokens.find((c)=>c.name.toLowerCase() === name.toLowerCase());
|
|
5990
|
+
}
|
|
5991
|
+
function formatTokenCategory(category) {
|
|
5992
|
+
const lines = [];
|
|
5993
|
+
lines.push(`# ${category.name}`);
|
|
5994
|
+
if (category.description) lines.push('', category.description);
|
|
5995
|
+
lines.push('', `Total: ${category.tokens.length} tokens`, '');
|
|
5996
|
+
lines.push('| Token | Value | Utility |');
|
|
5997
|
+
lines.push('|-------|-------|---------|');
|
|
5998
|
+
for (const token of category.tokens){
|
|
5999
|
+
const utility = tokenUtilities(token.name).map((u)=>`\`${u}\``).join(' ');
|
|
6000
|
+
lines.push(`| \`${token.name}\` | \`${token.value}\` | ${utility} |`);
|
|
6001
|
+
}
|
|
6002
|
+
return lines.join('\n');
|
|
6003
|
+
}
|
|
6004
|
+
const ALIASES = {
|
|
6005
|
+
modal: [
|
|
6006
|
+
'Dialog'
|
|
6007
|
+
],
|
|
6008
|
+
popup: [
|
|
6009
|
+
'Dialog',
|
|
6010
|
+
'Popover'
|
|
6011
|
+
],
|
|
6012
|
+
confirm: [
|
|
6013
|
+
'Dialog'
|
|
6014
|
+
],
|
|
6015
|
+
confirmation: [
|
|
6016
|
+
'Dialog'
|
|
6017
|
+
],
|
|
6018
|
+
sidebar: [
|
|
6019
|
+
'RemoteShell',
|
|
6020
|
+
'NavRail',
|
|
6021
|
+
'AppShell',
|
|
6022
|
+
'Drawer'
|
|
6023
|
+
],
|
|
6024
|
+
'side panel': [
|
|
6025
|
+
'Drawer'
|
|
6026
|
+
],
|
|
6027
|
+
sheet: [
|
|
6028
|
+
'Drawer'
|
|
6029
|
+
],
|
|
6030
|
+
flyout: [
|
|
6031
|
+
'Drawer',
|
|
6032
|
+
'Popover'
|
|
6033
|
+
],
|
|
6034
|
+
navigation: [
|
|
6035
|
+
'NavRail',
|
|
6036
|
+
'RemoteShell',
|
|
6037
|
+
'TopHeader',
|
|
6038
|
+
'Breadcrumbs'
|
|
6039
|
+
],
|
|
6040
|
+
nav: [
|
|
6041
|
+
'NavRail',
|
|
6042
|
+
'RemoteShell'
|
|
6043
|
+
],
|
|
6044
|
+
header: [
|
|
6045
|
+
'TopHeader',
|
|
6046
|
+
'Heading'
|
|
6047
|
+
],
|
|
6048
|
+
layout: [
|
|
6049
|
+
'Page',
|
|
6050
|
+
'AppShell',
|
|
6051
|
+
'Stack',
|
|
6052
|
+
'Flex'
|
|
6053
|
+
],
|
|
6054
|
+
toast: [
|
|
6055
|
+
'Toast'
|
|
6056
|
+
],
|
|
6057
|
+
snackbar: [
|
|
6058
|
+
'Toast'
|
|
6059
|
+
],
|
|
6060
|
+
notification: [
|
|
6061
|
+
'Toast',
|
|
6062
|
+
'Alert',
|
|
6063
|
+
'Banner',
|
|
6064
|
+
'NotificationCenter'
|
|
6065
|
+
],
|
|
6066
|
+
callout: [
|
|
6067
|
+
'Alert',
|
|
6068
|
+
'Banner'
|
|
6069
|
+
],
|
|
6070
|
+
dropdown: [
|
|
6071
|
+
'Select',
|
|
6072
|
+
'DropdownMenu'
|
|
6073
|
+
],
|
|
6074
|
+
combobox: [
|
|
6075
|
+
'Select'
|
|
6076
|
+
],
|
|
6077
|
+
autocomplete: [
|
|
6078
|
+
'Select'
|
|
6079
|
+
],
|
|
6080
|
+
'context menu': [
|
|
6081
|
+
'DropdownMenu'
|
|
6082
|
+
],
|
|
6083
|
+
menu: [
|
|
6084
|
+
'DropdownMenu'
|
|
6085
|
+
],
|
|
6086
|
+
'empty state': [
|
|
6087
|
+
'EmptyState'
|
|
6088
|
+
],
|
|
6089
|
+
'no data': [
|
|
6090
|
+
'EmptyState'
|
|
6091
|
+
],
|
|
6092
|
+
'date picker': [
|
|
6093
|
+
'DateInput',
|
|
6094
|
+
'DateRangeInput',
|
|
6095
|
+
'Calendar'
|
|
6096
|
+
],
|
|
6097
|
+
datepicker: [
|
|
6098
|
+
'DateInput',
|
|
6099
|
+
'DateRangeInput',
|
|
6100
|
+
'Calendar'
|
|
6101
|
+
],
|
|
6102
|
+
'date range': [
|
|
6103
|
+
'DateRangeInput'
|
|
6104
|
+
],
|
|
6105
|
+
'time picker': [
|
|
6106
|
+
'TimeInput'
|
|
6107
|
+
],
|
|
6108
|
+
grid: [
|
|
6109
|
+
'Table'
|
|
6110
|
+
],
|
|
6111
|
+
bulk: [
|
|
6112
|
+
'Table'
|
|
6113
|
+
],
|
|
6114
|
+
'action bar': [
|
|
6115
|
+
'Table'
|
|
6116
|
+
],
|
|
6117
|
+
'row selection': [
|
|
6118
|
+
'Table'
|
|
6119
|
+
],
|
|
6120
|
+
'data table': [
|
|
6121
|
+
'Table'
|
|
6122
|
+
],
|
|
6123
|
+
datagrid: [
|
|
6124
|
+
'Table'
|
|
6125
|
+
],
|
|
6126
|
+
chart: [
|
|
6127
|
+
'SimpleCharts'
|
|
6128
|
+
],
|
|
6129
|
+
graph: [
|
|
6130
|
+
'SimpleCharts'
|
|
6131
|
+
],
|
|
6132
|
+
spinner: [
|
|
6133
|
+
'Loader'
|
|
6134
|
+
],
|
|
6135
|
+
toggle: [
|
|
6136
|
+
'Switch',
|
|
6137
|
+
'ToggleButton'
|
|
6138
|
+
],
|
|
6139
|
+
upload: [
|
|
6140
|
+
'FileUpload'
|
|
6141
|
+
],
|
|
6142
|
+
dropzone: [
|
|
6143
|
+
'FileUpload'
|
|
6144
|
+
],
|
|
6145
|
+
hint: [
|
|
6146
|
+
'Tooltip'
|
|
6147
|
+
],
|
|
6148
|
+
chip: [
|
|
6149
|
+
'Tag',
|
|
6150
|
+
'Badge'
|
|
6151
|
+
],
|
|
6152
|
+
pill: [
|
|
6153
|
+
'Tag',
|
|
6154
|
+
'Badge'
|
|
6155
|
+
],
|
|
6156
|
+
label: [
|
|
6157
|
+
'Tag',
|
|
6158
|
+
'Badge'
|
|
6159
|
+
],
|
|
6160
|
+
form: [
|
|
6161
|
+
'Field',
|
|
6162
|
+
'Input'
|
|
6163
|
+
],
|
|
6164
|
+
'form field': [
|
|
6165
|
+
'Field'
|
|
6166
|
+
],
|
|
6167
|
+
'text field': [
|
|
6168
|
+
'Input'
|
|
6169
|
+
],
|
|
6170
|
+
multiline: [
|
|
6171
|
+
'Textarea'
|
|
6172
|
+
],
|
|
6173
|
+
search: [
|
|
6174
|
+
'SearchInput',
|
|
6175
|
+
'SearchModal'
|
|
6176
|
+
],
|
|
6177
|
+
'command palette': [
|
|
6178
|
+
'SearchModal'
|
|
6179
|
+
],
|
|
6180
|
+
'code block': [
|
|
6181
|
+
'CodeSnippet',
|
|
6182
|
+
'CodeEditor'
|
|
6183
|
+
],
|
|
6184
|
+
copy: [
|
|
6185
|
+
'Copyable'
|
|
6186
|
+
],
|
|
6187
|
+
wizard: [
|
|
6188
|
+
'Stepper'
|
|
6189
|
+
],
|
|
6190
|
+
collapse: [
|
|
6191
|
+
'Accordion'
|
|
6192
|
+
],
|
|
6193
|
+
collapsible: [
|
|
6194
|
+
'Accordion'
|
|
6195
|
+
],
|
|
6196
|
+
disclosure: [
|
|
6197
|
+
'Accordion'
|
|
6198
|
+
],
|
|
6199
|
+
paginator: [
|
|
6200
|
+
'Pagination'
|
|
6201
|
+
],
|
|
6202
|
+
otp: [
|
|
6203
|
+
'InputOTP'
|
|
6204
|
+
],
|
|
6205
|
+
divider: [
|
|
6206
|
+
'Separator'
|
|
6207
|
+
],
|
|
6208
|
+
'keyboard shortcut': [
|
|
6209
|
+
'Kbd'
|
|
6210
|
+
],
|
|
6211
|
+
hotkey: [
|
|
6212
|
+
'Kbd'
|
|
6213
|
+
],
|
|
6214
|
+
filter: [
|
|
6215
|
+
'FilterInput',
|
|
6216
|
+
'FilterDropdown',
|
|
6217
|
+
'FilterCascade'
|
|
6218
|
+
],
|
|
6219
|
+
status: [
|
|
6220
|
+
'Badge',
|
|
6221
|
+
'Indicator'
|
|
6222
|
+
]
|
|
6223
|
+
};
|
|
6224
|
+
const STOP_WORDS = new Set([
|
|
6225
|
+
'a',
|
|
6226
|
+
'an',
|
|
6227
|
+
'the',
|
|
6228
|
+
'for',
|
|
6229
|
+
'with',
|
|
6230
|
+
'of',
|
|
6231
|
+
'to',
|
|
6232
|
+
'in',
|
|
6233
|
+
'and',
|
|
6234
|
+
'or',
|
|
6235
|
+
'component',
|
|
6236
|
+
'ui'
|
|
6237
|
+
]);
|
|
6238
|
+
const singular = (w)=>w.length > 4 && w.endsWith('ies') ? `${w.slice(0, -3)}y` : w.length > 3 && w.endsWith('s') && !w.endsWith('ss') ? w.slice(0, -1) : w;
|
|
6239
|
+
const words = (text)=>text.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2').toLowerCase().split(/[^a-z0-9]+/).filter((w)=>w && !STOP_WORDS.has(w)).map(singular);
|
|
6240
|
+
const coverage = (query, field)=>{
|
|
6241
|
+
if (0 === query.length || 0 === field.length) return 0;
|
|
6242
|
+
const hits = query.filter((q)=>field.some((f)=>f === q || q.length >= 4 && f.startsWith(q)));
|
|
6243
|
+
return hits.length / query.length;
|
|
6244
|
+
};
|
|
6245
|
+
function scoreComponent(component, query, aliasHits) {
|
|
6246
|
+
const q = query.trim().toLowerCase();
|
|
6247
|
+
const name = component.name.toLowerCase();
|
|
6248
|
+
if (name === q || words(component.name).join(' ') === words(query).join(' ')) return 100;
|
|
6249
|
+
if (aliasHits.has(component.name)) return 90;
|
|
6250
|
+
if (q && name.startsWith(q)) return 80;
|
|
6251
|
+
if (q.length >= 3 && name.includes(q)) return 60;
|
|
6252
|
+
const qw = words(query);
|
|
6253
|
+
const nameScore = 60 * coverage(qw, words(component.name));
|
|
6254
|
+
const descScore = 40 * coverage(qw, words(component.description ?? ''));
|
|
6255
|
+
const propScore = 30 * coverage(qw, component.props.flatMap((p)=>words(p.name)));
|
|
6256
|
+
const variantScore = 20 * coverage(qw, component.variants.flatMap((v)=>[
|
|
6257
|
+
...words(v.name),
|
|
6258
|
+
...v.options.flatMap(words)
|
|
6259
|
+
]));
|
|
6260
|
+
const subScore = 15 * coverage(qw, component.subComponents.flatMap((s)=>words(s.name)));
|
|
6261
|
+
return Math.round(Math.max(nameScore, descScore, propScore, variantScore, subScore));
|
|
6262
|
+
}
|
|
6263
|
+
function aliasMatches(query) {
|
|
6264
|
+
const q = ` ${words(query).join(' ')} `;
|
|
6265
|
+
const hits = new Set();
|
|
6266
|
+
for (const [phrase, targets] of Object.entries(ALIASES))if (q.includes(` ${words(phrase).join(' ')} `)) for (const t of targets)hits.add(t);
|
|
6267
|
+
return hits;
|
|
6268
|
+
}
|
|
6269
|
+
function searchComponents(metadata, query) {
|
|
6270
|
+
const aliasHits = aliasMatches(query);
|
|
6271
|
+
return metadata.components.map((c)=>({
|
|
6272
|
+
name: c.name,
|
|
6273
|
+
description: c.description,
|
|
6274
|
+
importPath: c.importPath,
|
|
6275
|
+
score: scoreComponent(c, query, aliasHits)
|
|
6276
|
+
})).filter((r)=>r.score > 0).sort((a, b)=>b.score - a.score);
|
|
6277
|
+
}
|
|
5231
6278
|
async function main() {
|
|
5232
6279
|
const log = (...args)=>console.error('[wallarm-ds-mcp]', ...args);
|
|
5233
6280
|
log('Loading metadata...');
|
|
5234
|
-
const metadata = await loadMetadata();
|
|
5235
|
-
|
|
6281
|
+
const { metadata, metadataPath, source } = await loadMetadata();
|
|
6282
|
+
const sourceLine = describeSource(source, metadata.version);
|
|
6283
|
+
let guidelines = loadGuidelines(metadataPath);
|
|
6284
|
+
let guidelinesSource = '';
|
|
6285
|
+
if (!guidelines && 'installed' === source.kind) {
|
|
6286
|
+
guidelines = loadBundledGuidelines();
|
|
6287
|
+
if (guidelines) guidelinesSource = `\n\n_Guidelines: bundled snapshot v${guidelines.version} — your installed @wallarm-org/design-system v${metadata.version} ships no guidelines.json._`;
|
|
6288
|
+
}
|
|
6289
|
+
log(sourceLine, `(${metadataPath})`);
|
|
6290
|
+
log(`Loaded v${metadata.version}: ${metadata.components.length} components, ${metadata.tokens.length} token categories, ${guidelines ? `${guidelines.guidelines.length} guidelines` : 'no guidelines.json (bundled rules)'}`);
|
|
6291
|
+
const noGuidelines = `Guidelines are not available: @wallarm-org/design-system v${metadata.version} ships no guidelines.json. Upgrade the design system to get foundations and patterns.`;
|
|
5236
6292
|
const server = new McpServer({
|
|
5237
6293
|
name: 'wallarm-design-system',
|
|
5238
6294
|
version: metadata.version
|
|
6295
|
+
}, {
|
|
6296
|
+
instructions: `${buildInstructions(guidelines?.rules ?? rulesraw_namespaceObject, sourceLine)}${guidelinesSource}`
|
|
5239
6297
|
});
|
|
6298
|
+
const notFound = (name)=>{
|
|
6299
|
+
const suggestion = searchComponents(metadata, name).slice(0, 3);
|
|
6300
|
+
const hint = suggestion.length > 0 ? `\n\nDid you mean: ${suggestion.map((s)=>s.name).join(', ')}?` : '';
|
|
6301
|
+
return {
|
|
6302
|
+
content: [
|
|
6303
|
+
{
|
|
6304
|
+
type: 'text',
|
|
6305
|
+
text: `Component "${name}" not found.${hint}`
|
|
6306
|
+
}
|
|
6307
|
+
]
|
|
6308
|
+
};
|
|
6309
|
+
};
|
|
5240
6310
|
server.registerTool('search_component', {
|
|
5241
|
-
description: "
|
|
6311
|
+
description: 'Use when you need a UI element and do not know which DS component provides it (e.g. "modal", "date picker", "empty state", "sidebar"). Word-based search over names, descriptions, prop names, variants and common aliases; returns up to 10 components by relevance.',
|
|
5242
6312
|
inputSchema: {
|
|
5243
|
-
query: schemas_string().describe('
|
|
6313
|
+
query: schemas_string().describe('What you need — a component name, a UI concept ("toast", "dropdown") or a prop name')
|
|
5244
6314
|
}
|
|
5245
6315
|
}, async ({ query })=>{
|
|
5246
6316
|
const results = searchComponents(metadata, query);
|
|
@@ -5266,35 +6336,68 @@ async function main() {
|
|
|
5266
6336
|
};
|
|
5267
6337
|
});
|
|
5268
6338
|
server.registerTool('get_component', {
|
|
5269
|
-
description:
|
|
6339
|
+
description: "Use when you know the component and need its API before writing code. Without `section` returns an overview: import, props, variants, sub-component names, example names, usage docs and related guidelines. Pass `section` for full detail: 'props', 'subcomponents', 'examples', 'docs/<Name>' or a sub-component name (e.g. 'TableColumnMenu'). Long sections are paginated — pass `page`.",
|
|
5270
6340
|
inputSchema: {
|
|
5271
|
-
name: schemas_string().describe('Component name (case-insensitive), e.g. "Button", "Alert", "Select"')
|
|
6341
|
+
name: schemas_string().describe('Component name (case-insensitive), e.g. "Button", "Alert", "Select"'),
|
|
6342
|
+
section: schemas_string().optional().describe("Omit for the overview; or 'props', 'subcomponents', 'examples', 'docs/<Name>', or a sub-component name"),
|
|
6343
|
+
page: schemas_number().int().min(1).optional().describe('Page of a paginated response, from 1')
|
|
5272
6344
|
}
|
|
5273
|
-
}, async ({ name })=>{
|
|
6345
|
+
}, async ({ name, section, page })=>{
|
|
5274
6346
|
const component = getComponent(metadata, name);
|
|
5275
6347
|
if (!component) {
|
|
5276
|
-
const
|
|
5277
|
-
|
|
6348
|
+
const owner = findSubComponentOwner(metadata, name);
|
|
6349
|
+
if (!owner) return notFound(name);
|
|
6350
|
+
const text = formatComponent(owner.component, {
|
|
6351
|
+
section: owner.sub.name,
|
|
6352
|
+
page,
|
|
6353
|
+
source: sourceLine
|
|
6354
|
+
});
|
|
5278
6355
|
return {
|
|
5279
6356
|
content: [
|
|
5280
6357
|
{
|
|
5281
6358
|
type: 'text',
|
|
5282
|
-
text
|
|
6359
|
+
text
|
|
5283
6360
|
}
|
|
5284
6361
|
]
|
|
5285
6362
|
};
|
|
5286
6363
|
}
|
|
6364
|
+
const related = guidelines ? formatRelatedGuidelines(relatedGuidelines(guidelines, component.name)) : '';
|
|
6365
|
+
const text = formatComponent(component, {
|
|
6366
|
+
section,
|
|
6367
|
+
page,
|
|
6368
|
+
related,
|
|
6369
|
+
source: sourceLine
|
|
6370
|
+
});
|
|
6371
|
+
return {
|
|
6372
|
+
content: [
|
|
6373
|
+
{
|
|
6374
|
+
type: 'text',
|
|
6375
|
+
text
|
|
6376
|
+
}
|
|
6377
|
+
]
|
|
6378
|
+
};
|
|
6379
|
+
});
|
|
6380
|
+
server.registerTool('get_example', {
|
|
6381
|
+
description: 'Use when you want working code for one usage of a component — copy the pattern instead of guessing composition. Example names come from the `get_component` overview; close names are matched, and a miss lists every example.',
|
|
6382
|
+
inputSchema: {
|
|
6383
|
+
component: schemas_string().describe('Component name, e.g. "Table", "SimpleCharts"'),
|
|
6384
|
+
example: schemas_string().describe('Example name from get_component, e.g. "WithPagination" or "AreaChart/Default"'),
|
|
6385
|
+
page: schemas_number().int().min(1).optional().describe('Page of a paginated response, from 1')
|
|
6386
|
+
}
|
|
6387
|
+
}, async ({ component: name, example, page })=>{
|
|
6388
|
+
const component = getComponent(metadata, name) ?? findSubComponentOwner(metadata, name)?.component;
|
|
6389
|
+
if (!component) return notFound(name);
|
|
5287
6390
|
return {
|
|
5288
6391
|
content: [
|
|
5289
6392
|
{
|
|
5290
6393
|
type: 'text',
|
|
5291
|
-
text:
|
|
6394
|
+
text: getExampleText(component, example, page)
|
|
5292
6395
|
}
|
|
5293
6396
|
]
|
|
5294
6397
|
};
|
|
5295
6398
|
});
|
|
5296
6399
|
server.registerTool('search_token', {
|
|
5297
|
-
description: '
|
|
6400
|
+
description: 'Use when styling needs a colour, spacing, radius, shadow or type value — find the token and the Tailwind class to write (e.g. "text-primary", "surface", "spacing-8", "#ff6633", "bg-bg-surface-2"). Returns up to 20 tokens with value and utility classes.',
|
|
5298
6401
|
inputSchema: {
|
|
5299
6402
|
query: schemas_string().describe('Search query — token name (e.g. "spacing-8"), value (e.g. "#ff6633"), or category (e.g. "colors-primary")')
|
|
5300
6403
|
}
|
|
@@ -5308,7 +6411,10 @@ async function main() {
|
|
|
5308
6411
|
}
|
|
5309
6412
|
]
|
|
5310
6413
|
};
|
|
5311
|
-
const text = results.slice(0, 20).map((r)
|
|
6414
|
+
const text = results.slice(0, 20).map((r)=>{
|
|
6415
|
+
const utilities = r.utilities.length > 0 ? ` → ${r.utilities.map((u)=>`\`${u}\``).join(' ')}` : '';
|
|
6416
|
+
return `- \`${r.name}\`: \`${r.value}\`${utilities} _(${r.category})_`;
|
|
6417
|
+
}).join('\n');
|
|
5312
6418
|
return {
|
|
5313
6419
|
content: [
|
|
5314
6420
|
{
|
|
@@ -5319,11 +6425,12 @@ async function main() {
|
|
|
5319
6425
|
};
|
|
5320
6426
|
});
|
|
5321
6427
|
server.registerTool('get_token_category', {
|
|
5322
|
-
description: '
|
|
6428
|
+
description: 'Use when you need the whole scale of one token category (e.g. all spacing steps, all semantic colours) with values and Tailwind classes. Large categories are paginated — pass `page`.',
|
|
5323
6429
|
inputSchema: {
|
|
5324
|
-
name: schemas_string().describe('Category name (case-insensitive), e.g. "spacing", "colors-primary", "shadow"')
|
|
6430
|
+
name: schemas_string().describe('Category name (case-insensitive), e.g. "spacing", "colors-primary", "shadow"'),
|
|
6431
|
+
page: schemas_number().int().min(1).optional().describe('Page of a paginated response, from 1')
|
|
5325
6432
|
}
|
|
5326
|
-
}, async ({ name })=>{
|
|
6433
|
+
}, async ({ name, page = 1 })=>{
|
|
5327
6434
|
const category = getTokenCategory(metadata, name);
|
|
5328
6435
|
if (!category) {
|
|
5329
6436
|
const categories = metadata.tokens.map((c)=>c.name);
|
|
@@ -5336,15 +6443,47 @@ async function main() {
|
|
|
5336
6443
|
]
|
|
5337
6444
|
};
|
|
5338
6445
|
}
|
|
6446
|
+
const text = paginate(formatTokenCategory(category), page, (next)=>`\`{ name: '${category.name}', page: ${next} }\``);
|
|
5339
6447
|
return {
|
|
5340
6448
|
content: [
|
|
5341
6449
|
{
|
|
5342
6450
|
type: 'text',
|
|
5343
|
-
text
|
|
6451
|
+
text
|
|
5344
6452
|
}
|
|
5345
6453
|
]
|
|
5346
6454
|
};
|
|
5347
6455
|
});
|
|
6456
|
+
server.registerTool('list_guidelines', {
|
|
6457
|
+
description: "Use when starting a screen or flow, or unsure about spacing, colour or typography rules — lists foundations and patterns with slug, title, description and components.",
|
|
6458
|
+
inputSchema: {
|
|
6459
|
+
kind: schemas_enum([
|
|
6460
|
+
'foundation',
|
|
6461
|
+
'pattern'
|
|
6462
|
+
]).optional().describe('Only list foundations or only patterns; omit for both')
|
|
6463
|
+
}
|
|
6464
|
+
}, async ({ kind })=>({
|
|
6465
|
+
content: [
|
|
6466
|
+
{
|
|
6467
|
+
type: 'text',
|
|
6468
|
+
text: guidelines ? `${formatGuidelineList(listGuidelines(guidelines, kind))}${guidelinesSource}` : noGuidelines
|
|
6469
|
+
}
|
|
6470
|
+
]
|
|
6471
|
+
}));
|
|
6472
|
+
server.registerTool('get_guideline', {
|
|
6473
|
+
description: "Use when composing a screen (read the matching pattern first) or applying a foundation — returns the guideline as markdown. Pass `section` to get one section only; `section: 'stories'` returns a pattern's full live-story code (paginated — pass `page`).",
|
|
6474
|
+
inputSchema: {
|
|
6475
|
+
slug: schemas_string().describe('Guideline slug from list_guidelines, e.g. "patterns/entity-table" or "foundations/spacing"'),
|
|
6476
|
+
section: schemas_string().optional().describe('Section id or heading, e.g. "rules", "examples", "states", "stories"'),
|
|
6477
|
+
page: schemas_number().int().min(1).optional().describe('Page of a paginated response, from 1')
|
|
6478
|
+
}
|
|
6479
|
+
}, async ({ slug, section, page })=>({
|
|
6480
|
+
content: [
|
|
6481
|
+
{
|
|
6482
|
+
type: 'text',
|
|
6483
|
+
text: guidelines ? `${getGuidelineText(guidelines, slug, section, page)}${guidelinesSource}` : noGuidelines
|
|
6484
|
+
}
|
|
6485
|
+
]
|
|
6486
|
+
}));
|
|
5348
6487
|
server.registerResource('component-list', 'ds://components', {
|
|
5349
6488
|
description: 'List of all Wallarm Design System components with import paths',
|
|
5350
6489
|
mimeType: 'text/markdown'
|
|
@@ -5390,7 +6529,9 @@ async function main() {
|
|
|
5390
6529
|
contents: [
|
|
5391
6530
|
{
|
|
5392
6531
|
uri: uri.href,
|
|
5393
|
-
text:
|
|
6532
|
+
text: formatComponent(component, {
|
|
6533
|
+
source: sourceLine
|
|
6534
|
+
}),
|
|
5394
6535
|
mimeType: 'text/markdown'
|
|
5395
6536
|
}
|
|
5396
6537
|
]
|