@slexkit/mcp 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -58
- package/dist/data/llms-components.txt +6 -21
- package/dist/data/llms-full.txt +7456 -6869
- package/dist/data/llms-runtime.txt +1704 -1433
- package/dist/data/llms-toolhost.txt +290 -284
- package/dist/data/llms.txt +19 -13
- package/dist/data/slexkit-ai-manifest.json +315 -294
- package/dist/data/standard/slex-capabilities.catalog.json +413 -0
- package/dist/data/standard/slex-component-catalog.json +4475 -0
- package/dist/data/standard/slex-conformance.json +248 -0
- package/dist/data/standard/slex-expression.schema.json +2116 -0
- package/dist/data/standard/slex-logic-profile.json +130 -0
- package/dist/data/standard/slex-standard-manifest.json +37 -0
- package/dist/index.js +852 -40
- package/dist/index.js.map +5 -5
- package/package.json +47 -47
package/dist/index.js
CHANGED
|
@@ -236,12 +236,15 @@ function stackLine(error) {
|
|
|
236
236
|
const parsed = Number(match[1]);
|
|
237
237
|
return Number.isFinite(parsed) ? Math.max(1, parsed - 2) : null;
|
|
238
238
|
}
|
|
239
|
-
function
|
|
239
|
+
function scanSourceState(source) {
|
|
240
240
|
const chars = [];
|
|
241
|
+
const stack = [];
|
|
242
|
+
const pairs = { "}": "{", "]": "[", ")": "(" };
|
|
241
243
|
let quote = "";
|
|
242
244
|
let escaped = false;
|
|
243
245
|
let line = 1;
|
|
244
246
|
let column = 0;
|
|
247
|
+
let mismatchedDelimiter;
|
|
245
248
|
for (let index = 0;index < source.length; index += 1) {
|
|
246
249
|
const char = source[index];
|
|
247
250
|
const next = source[index + 1];
|
|
@@ -284,9 +287,28 @@ function scanSource(source) {
|
|
|
284
287
|
escaped = false;
|
|
285
288
|
continue;
|
|
286
289
|
}
|
|
287
|
-
|
|
290
|
+
const located = { char, index, line, column };
|
|
291
|
+
chars.push(located);
|
|
292
|
+
if (char === "{" || char === "[" || char === "(") {
|
|
293
|
+
stack.push(located);
|
|
294
|
+
} else if (char === "}" || char === "]" || char === ")") {
|
|
295
|
+
const expected = pairs[char];
|
|
296
|
+
const opener = stack.pop();
|
|
297
|
+
if (!opener || opener.char !== expected) {
|
|
298
|
+
mismatchedDelimiter = located;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
288
301
|
}
|
|
289
|
-
return
|
|
302
|
+
return {
|
|
303
|
+
chars,
|
|
304
|
+
stack,
|
|
305
|
+
openQuote: quote || undefined,
|
|
306
|
+
escaped,
|
|
307
|
+
mismatchedDelimiter
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function scanSource(source) {
|
|
311
|
+
return scanSourceState(source).chars;
|
|
290
312
|
}
|
|
291
313
|
function tokenDiagnostic(source, message) {
|
|
292
314
|
const chars = scanSource(source);
|
|
@@ -322,26 +344,15 @@ function tokenDiagnostic(source, message) {
|
|
|
322
344
|
return null;
|
|
323
345
|
}
|
|
324
346
|
function delimiterDiagnostic(source) {
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
if (item.char === "}" || item.char === "]" || item.char === ")") {
|
|
333
|
-
const expected = pairs[item.char];
|
|
334
|
-
const opener2 = stack.pop();
|
|
335
|
-
if (!opener2 || opener2.char !== expected) {
|
|
336
|
-
return {
|
|
337
|
-
line: item.line,
|
|
338
|
-
column: item.column,
|
|
339
|
-
detail: `Unexpected closing delimiter ${item.char}.`
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
}
|
|
347
|
+
const scan = scanSourceState(source);
|
|
348
|
+
if (scan.mismatchedDelimiter) {
|
|
349
|
+
return {
|
|
350
|
+
line: scan.mismatchedDelimiter.line,
|
|
351
|
+
column: scan.mismatchedDelimiter.column,
|
|
352
|
+
detail: `Unexpected closing delimiter ${scan.mismatchedDelimiter.char}.`
|
|
353
|
+
};
|
|
343
354
|
}
|
|
344
|
-
const opener = stack.at(-1);
|
|
355
|
+
const opener = scan.stack.at(-1);
|
|
345
356
|
if (!opener)
|
|
346
357
|
return null;
|
|
347
358
|
const end = lineColumnAt(source, source.length);
|
|
@@ -359,6 +370,7 @@ function locateSyntaxError(source, error) {
|
|
|
359
370
|
const position = token ?? (delimiter && rawMessage.includes("Expected") ? delimiter : null) ?? (stack ? { line: stack, column: 1 } : null) ?? delimiter ?? { line: 1, column: 1 };
|
|
360
371
|
const detail = token ? "The parser failed at this token." : delimiter?.detail;
|
|
361
372
|
return {
|
|
373
|
+
code: "syntax",
|
|
362
374
|
message: rawMessage,
|
|
363
375
|
line: position.line,
|
|
364
376
|
column: position.column,
|
|
@@ -499,6 +511,7 @@ ${parseSource}
|
|
|
499
511
|
};
|
|
500
512
|
}
|
|
501
513
|
}
|
|
514
|
+
var SLEXKIT_VERSION = "0.4.0";
|
|
502
515
|
var SLEX_PROTOCOL_VERSION = "0.1";
|
|
503
516
|
var SINCE = "0.1.0";
|
|
504
517
|
function docs(type) {
|
|
@@ -719,12 +732,13 @@ var cardSpec = component({
|
|
|
719
732
|
type: "card",
|
|
720
733
|
category: "Layout",
|
|
721
734
|
title: "Card",
|
|
722
|
-
summary: "
|
|
723
|
-
description: "Use card to group related content on a
|
|
735
|
+
summary: "Grouping container for related content.",
|
|
736
|
+
description: "Use card to group related content on a framed surface.",
|
|
724
737
|
props: {
|
|
725
738
|
title: { type: "string", dynamic: true, description: "Card title." },
|
|
726
739
|
icon: { type: "string", description: "Icon name shown before the title." },
|
|
727
|
-
tone: { type: "string", values: semanticTones, description: "Optional semantic tone for the card surface." }
|
|
740
|
+
tone: { type: "string", values: semanticTones, description: "Optional semantic tone for the card surface." },
|
|
741
|
+
variant: { type: "string", values: ["tool"], description: "Use tool for ToolHost input cards with compact chrome." }
|
|
728
742
|
},
|
|
729
743
|
children: childContent,
|
|
730
744
|
examples: [example("card", {
|
|
@@ -1123,8 +1137,10 @@ var radioGroupSpec = component({
|
|
|
1123
1137
|
icon: { type: "string", description: "Icon name shown before the group label." },
|
|
1124
1138
|
options: { type: "array", description: "Options with label, value, and optional icon." },
|
|
1125
1139
|
"options[].icon": { type: "string", description: "Icon name shown before a single option label." },
|
|
1140
|
+
"options[].description": { type: "string", description: "Secondary text shown below the option label." },
|
|
1126
1141
|
disabled: { type: "boolean", default: false, dynamic: true, description: "Disable every radio option in the group." },
|
|
1127
1142
|
orientation: { type: "string", values: ["vertical", "horizontal"], default: "vertical", description: "Radio option layout direction." },
|
|
1143
|
+
variant: { type: "string", values: ["list"], description: "Use list for full-row option surfaces in ToolHost decision cards." },
|
|
1128
1144
|
haptic: { type: "boolean", default: true, description: "Enable vibration feedback on supported devices." },
|
|
1129
1145
|
haptics: { type: "boolean", default: true, description: "Alias for haptic." },
|
|
1130
1146
|
name: { type: "string", description: "Native radio group name shared by options." },
|
|
@@ -1163,7 +1179,8 @@ var rowSpec = component({
|
|
|
1163
1179
|
props: {
|
|
1164
1180
|
justify: { type: "string", values: ["start", "center", "end", "space-between", "space-around"], default: "start", description: "Main-axis distribution." },
|
|
1165
1181
|
align: { type: "string", values: ["start", "center", "end", "baseline", "stretch"], default: "center", description: "Cross-axis alignment." },
|
|
1166
|
-
gap: { type: "string", dynamic: true, description: "Spacing between children." }
|
|
1182
|
+
gap: { type: "string", dynamic: true, description: "Spacing between children." },
|
|
1183
|
+
variant: { type: "string", values: ["actions"], description: "Use actions for compact ToolHost action rows." }
|
|
1167
1184
|
},
|
|
1168
1185
|
children: childContent,
|
|
1169
1186
|
examples: [example("row", {
|
|
@@ -1328,12 +1345,43 @@ var statSpec = component({
|
|
|
1328
1345
|
}
|
|
1329
1346
|
})]
|
|
1330
1347
|
});
|
|
1348
|
+
var stepSpec = component({
|
|
1349
|
+
type: "step",
|
|
1350
|
+
category: "Tooling",
|
|
1351
|
+
title: "ToolHost Step",
|
|
1352
|
+
summary: "ToolHost-only step page for multi-part human input.",
|
|
1353
|
+
description: "Use step inside ToolHost templates as the current page in a multi-step function call. It is not a public display component.",
|
|
1354
|
+
props: {
|
|
1355
|
+
title: { type: "string", description: "Step title." },
|
|
1356
|
+
description: { type: "string", description: "Short helper text for the step." },
|
|
1357
|
+
index: { type: "string | number", description: "Visible step number." },
|
|
1358
|
+
total: { type: "string | number", description: "Total step count shown with index." },
|
|
1359
|
+
progress: { type: "string", description: "Explicit progress label, such as 1/2." },
|
|
1360
|
+
state: { type: "string", description: "Optional visual state such as current or completed." }
|
|
1361
|
+
},
|
|
1362
|
+
children: childContent,
|
|
1363
|
+
examples: [example("step", {
|
|
1364
|
+
namespace: "doc_step_toolhost",
|
|
1365
|
+
layout: {
|
|
1366
|
+
"step:strategy": {
|
|
1367
|
+
title: "Release strategy",
|
|
1368
|
+
index: 1,
|
|
1369
|
+
"radio-group:choice": {
|
|
1370
|
+
options: [
|
|
1371
|
+
{ label: "Canary", value: "canary" },
|
|
1372
|
+
{ label: "Full", value: "full" }
|
|
1373
|
+
]
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
})]
|
|
1378
|
+
});
|
|
1331
1379
|
var submitSpec = component({
|
|
1332
1380
|
type: "submit",
|
|
1333
|
-
category: "
|
|
1381
|
+
category: "Tooling",
|
|
1334
1382
|
title: "Submit",
|
|
1335
1383
|
summary: "ToolHost submit and ignore controls.",
|
|
1336
|
-
description: "Use submit inside
|
|
1384
|
+
description: "Use submit only inside ToolHost templates to return selected state fields to the host.",
|
|
1337
1385
|
props: {
|
|
1338
1386
|
submitLabel: { type: "string", default: "Submit", description: "Submit button text." },
|
|
1339
1387
|
ignoreLabel: { type: "string", default: "Ignore", description: "Ignore button text." },
|
|
@@ -1549,6 +1597,7 @@ var componentSpecs = [
|
|
|
1549
1597
|
selectSpec,
|
|
1550
1598
|
sliderSpec,
|
|
1551
1599
|
statSpec,
|
|
1600
|
+
stepSpec,
|
|
1552
1601
|
submitSpec,
|
|
1553
1602
|
switchSpec,
|
|
1554
1603
|
tableSpec,
|
|
@@ -1556,8 +1605,19 @@ var componentSpecs = [
|
|
|
1556
1605
|
textSpec,
|
|
1557
1606
|
toastSpec
|
|
1558
1607
|
];
|
|
1559
|
-
var
|
|
1608
|
+
var internalComponentTypes = new Set(["submit", "step"]);
|
|
1609
|
+
var publicComponentSpecs = componentSpecs.filter((spec) => !internalComponentTypes.has(spec.type));
|
|
1610
|
+
var publicComponentTypes = publicComponentSpecs.map((spec) => spec.type);
|
|
1560
1611
|
var componentSpecByType = new Map(componentSpecs.map((spec) => [spec.type, spec]));
|
|
1612
|
+
var slexkitExpressionContext = [
|
|
1613
|
+
{ name: "g", scope: "always", summary: "Reactive state proxy." },
|
|
1614
|
+
{ name: "std", scope: "always", summary: "Pure deterministic SlexKit standard library." },
|
|
1615
|
+
{ name: "api", scope: "host-injected", summary: "Host or secure runtime capability object." },
|
|
1616
|
+
{ name: "$event", scope: "event handlers", summary: "Event payload for on* handlers." },
|
|
1617
|
+
{ name: "$item", scope: "$for", summary: "Current array item." },
|
|
1618
|
+
{ name: "$index", scope: "$for", summary: "Current item index." },
|
|
1619
|
+
{ name: "$key", scope: "$for", summary: "Current item key." }
|
|
1620
|
+
];
|
|
1561
1621
|
var slexkitStdlibDocs = [
|
|
1562
1622
|
{
|
|
1563
1623
|
name: "math",
|
|
@@ -1624,16 +1684,499 @@ var slexkitRuntimeCapabilities = [
|
|
|
1624
1684
|
];
|
|
1625
1685
|
var slexkitStdlibFunctionNames = slexkitStdlibDocs.flatMap((namespace) => namespace.functions.map((fn) => fn.name));
|
|
1626
1686
|
var slexkitRuntimeCapabilityNames = slexkitRuntimeCapabilities.map((capability) => capability.name);
|
|
1687
|
+
var SLEX_SCHEMA_VERSION = "2026-06";
|
|
1688
|
+
var SLEX_LOGIC_PROFILE_VERSION = "0.1";
|
|
1689
|
+
var SLEX_STANDARD_ARTIFACTS = [
|
|
1690
|
+
"slex-expression.schema.json",
|
|
1691
|
+
"slex-component-catalog.json",
|
|
1692
|
+
"slex-logic-profile.json",
|
|
1693
|
+
"slex-capabilities.catalog.json",
|
|
1694
|
+
"slex-conformance.json",
|
|
1695
|
+
"slex-standard-manifest.json"
|
|
1696
|
+
];
|
|
1697
|
+
var slexReservedContextNames = [
|
|
1698
|
+
"g",
|
|
1699
|
+
"std",
|
|
1700
|
+
"api",
|
|
1701
|
+
"$event",
|
|
1702
|
+
"$item",
|
|
1703
|
+
"$index",
|
|
1704
|
+
"$key"
|
|
1705
|
+
];
|
|
1706
|
+
var slexNativeSecureCapabilities = [
|
|
1707
|
+
"fetch",
|
|
1708
|
+
"XMLHttpRequest",
|
|
1709
|
+
"WebSocket",
|
|
1710
|
+
"setTimeout",
|
|
1711
|
+
"setInterval",
|
|
1712
|
+
"requestAnimationFrame"
|
|
1713
|
+
];
|
|
1714
|
+
function hashStandardText(source) {
|
|
1715
|
+
let hash = 2166136261;
|
|
1716
|
+
for (let index = 0;index < source.length; index += 1) {
|
|
1717
|
+
hash ^= source.charCodeAt(index);
|
|
1718
|
+
hash = Math.imul(hash, 16777619);
|
|
1719
|
+
}
|
|
1720
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
1721
|
+
}
|
|
1722
|
+
function stableJson(value) {
|
|
1723
|
+
return `${JSON.stringify(value, null, 2)}
|
|
1724
|
+
`;
|
|
1725
|
+
}
|
|
1726
|
+
function componentHash(value) {
|
|
1727
|
+
return hashStandardText(JSON.stringify(value));
|
|
1728
|
+
}
|
|
1729
|
+
function propJsonSchema(prop) {
|
|
1730
|
+
if (prop.values?.length) {
|
|
1731
|
+
return {
|
|
1732
|
+
enum: [...prop.values],
|
|
1733
|
+
description: prop.description,
|
|
1734
|
+
default: prop.default
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
const typeMap = {
|
|
1738
|
+
string: { type: "string" },
|
|
1739
|
+
number: { type: "number" },
|
|
1740
|
+
boolean: { type: "boolean" },
|
|
1741
|
+
array: { type: "array" },
|
|
1742
|
+
"string[]": { type: "array", items: { type: "string" } },
|
|
1743
|
+
"string | number": { type: ["string", "number"] },
|
|
1744
|
+
"string | string[]": {
|
|
1745
|
+
anyOf: [
|
|
1746
|
+
{ type: "string" },
|
|
1747
|
+
{ type: "array", items: { type: "string" } }
|
|
1748
|
+
]
|
|
1749
|
+
},
|
|
1750
|
+
"object | string": {
|
|
1751
|
+
anyOf: [
|
|
1752
|
+
{ type: "object", additionalProperties: true },
|
|
1753
|
+
{ type: "string" }
|
|
1754
|
+
]
|
|
1755
|
+
},
|
|
1756
|
+
"write-expression": { type: "string", xSlexkitExpressionKind: "write" }
|
|
1757
|
+
};
|
|
1758
|
+
return {
|
|
1759
|
+
...typeMap[prop.type] ?? { description: `SlexKit prop type '${prop.type}'.` },
|
|
1760
|
+
description: prop.description,
|
|
1761
|
+
default: prop.default
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1764
|
+
function componentPropSchema(spec) {
|
|
1765
|
+
const properties = {};
|
|
1766
|
+
const required = [];
|
|
1767
|
+
for (const [name, prop] of Object.entries(spec.props)) {
|
|
1768
|
+
properties[name] = propJsonSchema(prop);
|
|
1769
|
+
if (prop.dynamic) {
|
|
1770
|
+
properties[`$${name}`] = {
|
|
1771
|
+
type: "string",
|
|
1772
|
+
description: `Dynamic expression for '${name}'. Evaluated with the Slex logic profile.`,
|
|
1773
|
+
xSlexkitExpressionKind: "read"
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
if (prop.required)
|
|
1777
|
+
required.push(name);
|
|
1778
|
+
}
|
|
1779
|
+
return {
|
|
1780
|
+
type: "object",
|
|
1781
|
+
additionalProperties: true,
|
|
1782
|
+
properties,
|
|
1783
|
+
required,
|
|
1784
|
+
patternProperties: {
|
|
1785
|
+
"^[a-z][a-z0-9-]*:[A-Za-z0-9_$-]*$": { $ref: "#/$defs/componentNode" },
|
|
1786
|
+
"^on[A-Za-z_$][\\w$-]*$": {
|
|
1787
|
+
type: "string",
|
|
1788
|
+
description: "Write-pipe event handler expression.",
|
|
1789
|
+
xSlexkitExpressionKind: "write"
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
function expressionSchema(packageVersion) {
|
|
1795
|
+
const componentKeyPattern = "^[a-z][a-z0-9-]*:[A-Za-z0-9_$-]*$";
|
|
1796
|
+
return {
|
|
1797
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
1798
|
+
$id: "https://slexkit.dev/standard/slex-expression.schema.json",
|
|
1799
|
+
title: "Slex Expression",
|
|
1800
|
+
description: "Structural schema for SlexKit logic-bearing Markdown UI artifacts. JavaScript expression semantics are defined by slex-logic-profile.json.",
|
|
1801
|
+
type: "object",
|
|
1802
|
+
additionalProperties: true,
|
|
1803
|
+
properties: {
|
|
1804
|
+
slex: {
|
|
1805
|
+
type: "string",
|
|
1806
|
+
const: SLEX_PROTOCOL_VERSION,
|
|
1807
|
+
description: "Optional public Slex protocol marker."
|
|
1808
|
+
},
|
|
1809
|
+
namespace: {
|
|
1810
|
+
type: "string",
|
|
1811
|
+
minLength: 1,
|
|
1812
|
+
description: "State namespace. Bare component trees default to namespace 'default'."
|
|
1813
|
+
},
|
|
1814
|
+
g: {
|
|
1815
|
+
$ref: "#/$defs/stateObject"
|
|
1816
|
+
},
|
|
1817
|
+
layout: {
|
|
1818
|
+
$ref: "#/$defs/layoutTree",
|
|
1819
|
+
description: "Component tree keyed by 'type:identifier'."
|
|
1820
|
+
}
|
|
1821
|
+
},
|
|
1822
|
+
patternProperties: {
|
|
1823
|
+
[componentKeyPattern]: {
|
|
1824
|
+
$ref: "#/$defs/componentNode"
|
|
1825
|
+
}
|
|
1826
|
+
},
|
|
1827
|
+
$defs: {
|
|
1828
|
+
componentKey: {
|
|
1829
|
+
type: "string",
|
|
1830
|
+
pattern: componentKeyPattern
|
|
1831
|
+
},
|
|
1832
|
+
directive: {
|
|
1833
|
+
type: "string",
|
|
1834
|
+
description: "$if, $for, and $key directive expressions are JavaScript expression strings."
|
|
1835
|
+
},
|
|
1836
|
+
readPipe: {
|
|
1837
|
+
type: "string",
|
|
1838
|
+
description: "JavaScript expression evaluated against g, std, api, component state, and loop context.",
|
|
1839
|
+
xSlexkitExpressionKind: "read"
|
|
1840
|
+
},
|
|
1841
|
+
writePipe: {
|
|
1842
|
+
type: "string",
|
|
1843
|
+
description: "JavaScript statements executed with $event and the Slex expression context.",
|
|
1844
|
+
xSlexkitExpressionKind: "write"
|
|
1845
|
+
},
|
|
1846
|
+
stateObject: {
|
|
1847
|
+
type: "object",
|
|
1848
|
+
additionalProperties: true,
|
|
1849
|
+
description: "Mutable artifact state and helper functions. Source form may contain JavaScript functions."
|
|
1850
|
+
},
|
|
1851
|
+
layoutTree: {
|
|
1852
|
+
type: "object",
|
|
1853
|
+
propertyNames: { $ref: "#/$defs/componentKey" },
|
|
1854
|
+
additionalProperties: { $ref: "#/$defs/componentNode" }
|
|
1855
|
+
},
|
|
1856
|
+
componentNode: {
|
|
1857
|
+
type: "object",
|
|
1858
|
+
additionalProperties: true,
|
|
1859
|
+
properties: {
|
|
1860
|
+
$if: { $ref: "#/$defs/directive" },
|
|
1861
|
+
$for: { $ref: "#/$defs/directive" },
|
|
1862
|
+
$key: { $ref: "#/$defs/directive" }
|
|
1863
|
+
},
|
|
1864
|
+
patternProperties: {
|
|
1865
|
+
"^\\$[A-Za-z_$][\\w$-]*$": { $ref: "#/$defs/readPipe" },
|
|
1866
|
+
"^on[A-Za-z_$][\\w$-]*$": { $ref: "#/$defs/writePipe" },
|
|
1867
|
+
[componentKeyPattern]: { $ref: "#/$defs/componentNode" }
|
|
1868
|
+
}
|
|
1869
|
+
},
|
|
1870
|
+
components: Object.fromEntries(publicComponentSpecs.map((spec) => [spec.type, componentPropSchema(spec)]))
|
|
1871
|
+
},
|
|
1872
|
+
xSlexkit: {
|
|
1873
|
+
packageVersion,
|
|
1874
|
+
schemaVersion: SLEX_SCHEMA_VERSION,
|
|
1875
|
+
protocolVersion: SLEX_PROTOCOL_VERSION,
|
|
1876
|
+
logicProfileVersion: SLEX_LOGIC_PROFILE_VERSION,
|
|
1877
|
+
sourceFormat: "JavaScript object literal embedded in explicit Markdown ```slex fences"
|
|
1878
|
+
}
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1881
|
+
function componentCatalog(packageVersion) {
|
|
1882
|
+
return {
|
|
1883
|
+
name: "slex-component-catalog",
|
|
1884
|
+
packageName: "slexkit",
|
|
1885
|
+
version: packageVersion,
|
|
1886
|
+
schemaVersion: SLEX_SCHEMA_VERSION,
|
|
1887
|
+
protocolVersion: SLEX_PROTOCOL_VERSION,
|
|
1888
|
+
componentCount: publicComponentTypes.length,
|
|
1889
|
+
components: publicComponentSpecs.map((spec) => ({
|
|
1890
|
+
type: spec.type,
|
|
1891
|
+
category: spec.category,
|
|
1892
|
+
status: spec.status,
|
|
1893
|
+
state: spec.state,
|
|
1894
|
+
since: spec.since,
|
|
1895
|
+
title: spec.title,
|
|
1896
|
+
summary: spec.summary,
|
|
1897
|
+
description: spec.description,
|
|
1898
|
+
props: spec.props,
|
|
1899
|
+
children: spec.children,
|
|
1900
|
+
examples: spec.examples,
|
|
1901
|
+
docs: spec.docs,
|
|
1902
|
+
jsonSchema: componentPropSchema(spec),
|
|
1903
|
+
hash: componentHash(spec)
|
|
1904
|
+
}))
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
function logicProfile(packageVersion) {
|
|
1908
|
+
return {
|
|
1909
|
+
name: "slex-logic-profile",
|
|
1910
|
+
packageName: "slexkit",
|
|
1911
|
+
version: packageVersion,
|
|
1912
|
+
profileVersion: SLEX_LOGIC_PROFILE_VERSION,
|
|
1913
|
+
schemaVersion: SLEX_SCHEMA_VERSION,
|
|
1914
|
+
protocolVersion: SLEX_PROTOCOL_VERSION,
|
|
1915
|
+
positioning: "Slex is a Markdown-native logic-bearing UI artifact: component structure is declarative, while local state and interaction logic are expressed through a constrained JavaScript expression profile.",
|
|
1916
|
+
sourceFormat: {
|
|
1917
|
+
fenceLanguage: "slex",
|
|
1918
|
+
acceptedForms: ["Slex expression envelope", "bare component tree shorthand", "state-only envelope"],
|
|
1919
|
+
plainJsonOnly: false
|
|
1920
|
+
},
|
|
1921
|
+
readPipes: {
|
|
1922
|
+
match: "Component prop keys beginning with '$', excluding structural directives.",
|
|
1923
|
+
valueType: "string",
|
|
1924
|
+
semantics: "Evaluated as JavaScript expressions with access to the Slex expression context."
|
|
1925
|
+
},
|
|
1926
|
+
writePipes: {
|
|
1927
|
+
match: "Component prop keys beginning with 'on'.",
|
|
1928
|
+
valueType: "string",
|
|
1929
|
+
semantics: "Executed as JavaScript statements. $event contains the emitted event data."
|
|
1930
|
+
},
|
|
1931
|
+
structuralDirectives: {
|
|
1932
|
+
$if: { valueType: "string", semantics: "Controls component existence." },
|
|
1933
|
+
$for: { valueType: "string", semantics: "Iterates over array-like values." },
|
|
1934
|
+
$key: { valueType: "string", semantics: "Provides the keyed reconciliation strategy for $for." }
|
|
1935
|
+
},
|
|
1936
|
+
expressionContext: slexkitExpressionContext,
|
|
1937
|
+
reservedContextNames: [...slexReservedContextNames],
|
|
1938
|
+
componentState: "Named components expose state according to their component catalog state mode: value, checked, enabled, readable, or none.",
|
|
1939
|
+
secureMode: {
|
|
1940
|
+
nativeCapabilitiesDenied: [...slexNativeSecureCapabilities],
|
|
1941
|
+
policyGatedApi: slexkitRuntimeCapabilityNames,
|
|
1942
|
+
guidance: "Use policy-gated api.* capabilities in secure mode; do not call native browser capabilities directly."
|
|
1943
|
+
},
|
|
1944
|
+
diagnostics: [
|
|
1945
|
+
"unsupported_protocol",
|
|
1946
|
+
"invalid_component_key",
|
|
1947
|
+
"invalid_directive_type",
|
|
1948
|
+
"unknown_component",
|
|
1949
|
+
"unknown_prop",
|
|
1950
|
+
"unknown_std_member",
|
|
1951
|
+
"unknown_api_member",
|
|
1952
|
+
"native_secure_capability",
|
|
1953
|
+
"reserved_context_shadowing"
|
|
1954
|
+
]
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
function capabilitiesCatalog(packageVersion) {
|
|
1958
|
+
return {
|
|
1959
|
+
name: "slex-capabilities-catalog",
|
|
1960
|
+
packageName: "slexkit",
|
|
1961
|
+
version: packageVersion,
|
|
1962
|
+
schemaVersion: SLEX_SCHEMA_VERSION,
|
|
1963
|
+
protocolVersion: SLEX_PROTOCOL_VERSION,
|
|
1964
|
+
expressionContext: slexkitExpressionContext,
|
|
1965
|
+
stdlib: slexkitStdlibDocs,
|
|
1966
|
+
stdlibFunctionNames: slexkitStdlibFunctionNames,
|
|
1967
|
+
apiCapabilities: slexkitRuntimeCapabilities,
|
|
1968
|
+
apiCapabilityNames: slexkitRuntimeCapabilityNames,
|
|
1969
|
+
secureNativeCapabilitiesDenied: [...slexNativeSecureCapabilities]
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
function conformance(packageVersion) {
|
|
1973
|
+
return {
|
|
1974
|
+
name: "slex-conformance",
|
|
1975
|
+
packageName: "slexkit",
|
|
1976
|
+
version: packageVersion,
|
|
1977
|
+
schemaVersion: SLEX_SCHEMA_VERSION,
|
|
1978
|
+
protocolVersion: SLEX_PROTOCOL_VERSION,
|
|
1979
|
+
logicProfileVersion: SLEX_LOGIC_PROFILE_VERSION,
|
|
1980
|
+
fixtures: [
|
|
1981
|
+
{
|
|
1982
|
+
id: "valid-full-envelope",
|
|
1983
|
+
kind: "valid",
|
|
1984
|
+
mode: "trusted",
|
|
1985
|
+
source: '{ slex: "0.1", namespace: "valid_full", g: { count: 0 }, layout: { "button:add": { label: "Add", onclick: "g.count++" }, "text:value": { "$text": "String(g.count)" } } }',
|
|
1986
|
+
expected: { ok: true, warnings: [] }
|
|
1987
|
+
},
|
|
1988
|
+
{
|
|
1989
|
+
id: "valid-dynamic-props",
|
|
1990
|
+
kind: "valid",
|
|
1991
|
+
mode: "trusted",
|
|
1992
|
+
source: `{ slex: "0.1", namespace: "dynamic_props", g: { name: "Slex" }, layout: { "text:greeting": { "$text": "'Hello, ' + g.name" } } }`,
|
|
1993
|
+
expected: { ok: true, warnings: [] }
|
|
1994
|
+
},
|
|
1995
|
+
{
|
|
1996
|
+
id: "valid-event-handler",
|
|
1997
|
+
kind: "valid",
|
|
1998
|
+
mode: "trusted",
|
|
1999
|
+
source: '{ slex: "0.1", namespace: "event_handler", g: { count: 0 }, layout: { "button:add": { label: "Add", onclick: "g.count = g.count + Number($event || 1)" } } }',
|
|
2000
|
+
expected: { ok: true, warnings: [] }
|
|
2001
|
+
},
|
|
2002
|
+
{
|
|
2003
|
+
id: "valid-loop-condition",
|
|
2004
|
+
kind: "valid",
|
|
2005
|
+
mode: "trusted",
|
|
2006
|
+
source: '{ slex: "0.1", namespace: "loop_condition", g: { visible: true, items: [{ id: "a", label: "A" }] }, layout: { "column:list": { "$if": "g.visible", "text:item": { "$for": "g.items", "$key": "$item.id", "$text": "$item.label" } } } }',
|
|
2007
|
+
expected: { ok: true, warnings: [] }
|
|
2008
|
+
},
|
|
2009
|
+
{
|
|
2010
|
+
id: "valid-stdlib",
|
|
2011
|
+
kind: "valid",
|
|
2012
|
+
mode: "trusted",
|
|
2013
|
+
source: '{ slex: "0.1", namespace: "stdlib", g: { done: 3, total: 4 }, layout: { "stat:progress": { label: "Progress", "$value": "std.format.percent(std.math.safeDivide(g.done, g.total), 1)" } } }',
|
|
2014
|
+
expected: { ok: true, warnings: [] }
|
|
2015
|
+
},
|
|
2016
|
+
{
|
|
2017
|
+
id: "valid-secure-api",
|
|
2018
|
+
kind: "valid",
|
|
2019
|
+
mode: "secure",
|
|
2020
|
+
source: '{ slex: "0.1", namespace: "secure_api", g: { status: "idle", async load() { const result = await api.fetch("/status"); this.status = String(result.status); } }, layout: { "button:load": { label: "Load", onclick: "g.load()" }, "text:status": { "$text": "g.status" } } }',
|
|
2021
|
+
expected: { ok: true, warnings: [] }
|
|
2022
|
+
},
|
|
2023
|
+
{
|
|
2024
|
+
id: "valid-bare-tree",
|
|
2025
|
+
kind: "valid",
|
|
2026
|
+
mode: "trusted",
|
|
2027
|
+
source: '{ "text:message": { text: "Bare tree" } }',
|
|
2028
|
+
expected: { ok: true, warnings: [] }
|
|
2029
|
+
},
|
|
2030
|
+
{
|
|
2031
|
+
id: "valid-state-only",
|
|
2032
|
+
kind: "valid",
|
|
2033
|
+
mode: "trusted",
|
|
2034
|
+
source: '{ slex: "0.1", namespace: "state_only", g: { ready: true } }',
|
|
2035
|
+
expected: { ok: true, warnings: [] }
|
|
2036
|
+
},
|
|
2037
|
+
{
|
|
2038
|
+
id: "warning-secure-native-capability",
|
|
2039
|
+
kind: "warning",
|
|
2040
|
+
mode: "secure",
|
|
2041
|
+
source: '{ slex: "0.1", namespace: "native_fetch", g: { load() { fetch("/x"); } }, layout: { "button:load": { label: "Load", onclick: "g.load()" } } }',
|
|
2042
|
+
expected: { ok: true, warnings: [{ code: "native_secure_capability", path: "g.load", value: "fetch" }] }
|
|
2043
|
+
},
|
|
2044
|
+
{
|
|
2045
|
+
id: "warning-secure-native-timer-websocket",
|
|
2046
|
+
kind: "warning",
|
|
2047
|
+
mode: "secure",
|
|
2048
|
+
source: '{ slex: "0.1", namespace: "native_timer_socket", g: { start() { setTimeout(() => {}, 10); const socket = new WebSocket("wss://example.com"); return socket; } }, layout: { "button:start": { label: "Start", onclick: "g.start()" } } }',
|
|
2049
|
+
expected: {
|
|
2050
|
+
ok: true,
|
|
2051
|
+
warnings: [
|
|
2052
|
+
{ code: "native_secure_capability", path: "g.start", value: "WebSocket" },
|
|
2053
|
+
{ code: "native_secure_capability", path: "g.start", value: "setTimeout" }
|
|
2054
|
+
]
|
|
2055
|
+
}
|
|
2056
|
+
},
|
|
2057
|
+
{
|
|
2058
|
+
id: "warning-unknown-component-prop",
|
|
2059
|
+
kind: "warning",
|
|
2060
|
+
mode: "trusted",
|
|
2061
|
+
source: '{ namespace: "unknowns", layout: { "madeup:thing": { madeUp: true }, "text:body": { madeUp: true } } }',
|
|
2062
|
+
expected: {
|
|
2063
|
+
ok: true,
|
|
2064
|
+
warnings: [
|
|
2065
|
+
{ code: "unknown_component", path: "layout.madeup:thing", value: "madeup" },
|
|
2066
|
+
{ code: "unknown_prop", path: "layout.text:body.madeUp", value: "madeUp" }
|
|
2067
|
+
]
|
|
2068
|
+
}
|
|
2069
|
+
},
|
|
2070
|
+
{
|
|
2071
|
+
id: "warning-invalid-component-key",
|
|
2072
|
+
kind: "warning",
|
|
2073
|
+
mode: "trusted",
|
|
2074
|
+
source: '{ namespace: "bad_key", layout: { "Text Bad:item": { text: "bad" } } }',
|
|
2075
|
+
expected: { ok: true, warnings: [{ code: "invalid_component_key", path: "layout.Text Bad:item", value: "Text Bad:item" }] }
|
|
2076
|
+
},
|
|
2077
|
+
{
|
|
2078
|
+
id: "warning-invalid-directive-type",
|
|
2079
|
+
kind: "warning",
|
|
2080
|
+
mode: "trusted",
|
|
2081
|
+
source: '{ namespace: "bad_directive", layout: { "text:message": { "$if": true, text: "bad" } } }',
|
|
2082
|
+
expected: { ok: true, warnings: [{ code: "invalid_directive_type", path: "layout.text:message.$if", value: "$if" }] }
|
|
2083
|
+
},
|
|
2084
|
+
{
|
|
2085
|
+
id: "warning-unsupported-protocol",
|
|
2086
|
+
kind: "warning",
|
|
2087
|
+
mode: "trusted",
|
|
2088
|
+
source: '{ slex: "9.9", namespace: "future", layout: { "text:message": { text: "future" } } }',
|
|
2089
|
+
expected: { ok: true, warnings: [{ code: "unsupported_protocol", path: "slex", value: "9.9" }] }
|
|
2090
|
+
},
|
|
2091
|
+
{
|
|
2092
|
+
id: "warning-reserved-context-shadowing",
|
|
2093
|
+
kind: "warning",
|
|
2094
|
+
mode: "trusted",
|
|
2095
|
+
source: '{ namespace: "shadow", g: { std: "bad" }, layout: { "text:api": { text: "shadow" } } }',
|
|
2096
|
+
expected: {
|
|
2097
|
+
ok: true,
|
|
2098
|
+
warnings: [
|
|
2099
|
+
{ code: "reserved_context_shadowing", path: "g.std", value: "std" },
|
|
2100
|
+
{ code: "reserved_context_shadowing", path: "layout.text:api", value: "api" }
|
|
2101
|
+
]
|
|
2102
|
+
}
|
|
2103
|
+
},
|
|
2104
|
+
{
|
|
2105
|
+
id: "warning-unknown-std-api",
|
|
2106
|
+
kind: "warning",
|
|
2107
|
+
mode: "secure",
|
|
2108
|
+
source: '{ slex: "0.1", namespace: "unknown_logic", g: { load() { api.socket(); } }, layout: { "text:value": { "$text": "std.math.nope(1)" } } }',
|
|
2109
|
+
expected: {
|
|
2110
|
+
ok: true,
|
|
2111
|
+
warnings: [
|
|
2112
|
+
{ code: "unknown_api_member", path: "g.load", value: "api.socket" },
|
|
2113
|
+
{ code: "unknown_std_member", path: "layout.text:value.$text", value: "std.math.nope" }
|
|
2114
|
+
]
|
|
2115
|
+
}
|
|
2116
|
+
},
|
|
2117
|
+
{
|
|
2118
|
+
id: "invalid-syntax",
|
|
2119
|
+
kind: "invalid",
|
|
2120
|
+
mode: "trusted",
|
|
2121
|
+
source: '{ namespace: "broken", layout: { "text:message": { text: "missing close" }',
|
|
2122
|
+
expected: { ok: false, diagnostic: "syntax" }
|
|
2123
|
+
}
|
|
2124
|
+
]
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
function createStandardArtifacts(packageVersion, generatedAt = new Date().toISOString()) {
|
|
2128
|
+
const artifacts = {
|
|
2129
|
+
"slex-expression.schema.json": expressionSchema(packageVersion),
|
|
2130
|
+
"slex-component-catalog.json": componentCatalog(packageVersion),
|
|
2131
|
+
"slex-logic-profile.json": logicProfile(packageVersion),
|
|
2132
|
+
"slex-capabilities.catalog.json": capabilitiesCatalog(packageVersion),
|
|
2133
|
+
"slex-conformance.json": conformance(packageVersion)
|
|
2134
|
+
};
|
|
2135
|
+
const artifactMetadata = Object.fromEntries(Object.entries(artifacts).map(([filename, artifact]) => [
|
|
2136
|
+
filename,
|
|
2137
|
+
{
|
|
2138
|
+
path: `/standard/${filename}`,
|
|
2139
|
+
hash: hashStandardText(stableJson(artifact))
|
|
2140
|
+
}
|
|
2141
|
+
]));
|
|
2142
|
+
const manifest = {
|
|
2143
|
+
name: "slex-standard-manifest",
|
|
2144
|
+
packageName: "slexkit",
|
|
2145
|
+
version: packageVersion,
|
|
2146
|
+
schemaVersion: SLEX_SCHEMA_VERSION,
|
|
2147
|
+
protocolVersion: SLEX_PROTOCOL_VERSION,
|
|
2148
|
+
logicProfileVersion: SLEX_LOGIC_PROFILE_VERSION,
|
|
2149
|
+
generatedAt,
|
|
2150
|
+
positioning: "Markdown-native logic-bearing UI artifact",
|
|
2151
|
+
compatibility: {
|
|
2152
|
+
keepsJavaScriptExpressionProfile: true,
|
|
2153
|
+
pureJsonUiProtocol: false,
|
|
2154
|
+
a2uiBridgePlanned: true
|
|
2155
|
+
},
|
|
2156
|
+
artifacts: artifactMetadata
|
|
2157
|
+
};
|
|
2158
|
+
const allArtifacts = {
|
|
2159
|
+
...artifacts,
|
|
2160
|
+
"slex-standard-manifest.json": manifest
|
|
2161
|
+
};
|
|
2162
|
+
return {
|
|
2163
|
+
artifacts: allArtifacts,
|
|
2164
|
+
files: Object.fromEntries(SLEX_STANDARD_ARTIFACTS.map((filename) => [filename, stableJson(allArtifacts[filename])]))
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
1627
2167
|
var componentSpecByType2 = new Map(componentSpecs.map((spec) => [spec.type, spec]));
|
|
1628
2168
|
var stdlibFunctions = new Set(slexkitStdlibFunctionNames);
|
|
1629
2169
|
var stdlibNamespaces = new Set(slexkitStdlibFunctionNames.map((name) => name.split(".").slice(0, 2).join(".")));
|
|
1630
2170
|
var apiMembers = new Set(slexkitRuntimeCapabilityNames);
|
|
1631
2171
|
var directiveProps = new Set(["$if", "$for", "$key"]);
|
|
2172
|
+
var reservedContextNames = new Set(slexReservedContextNames);
|
|
2173
|
+
var componentKeyPattern = /^[a-z][a-z0-9-]*:[A-Za-z0-9_$-]*$/;
|
|
1632
2174
|
var nativeSecureCapabilities = [
|
|
1633
2175
|
{ value: "fetch", pattern: /(?<!\.)\bfetch\s*\(/ },
|
|
1634
2176
|
{ value: "XMLHttpRequest", pattern: /\bXMLHttpRequest\b/ },
|
|
1635
2177
|
{ value: "WebSocket", pattern: /\bWebSocket\b/ },
|
|
1636
2178
|
{ value: "setTimeout", pattern: /(?<!\.)\bsetTimeout\s*\(/ },
|
|
2179
|
+
{ value: "setInterval", pattern: /(?<!\.)\bsetInterval\s*\(/ },
|
|
1637
2180
|
{ value: "requestAnimationFrame", pattern: /(?<!\.)\brequestAnimationFrame\s*\(/ }
|
|
1638
2181
|
];
|
|
1639
2182
|
function collectMemberUsage(source, root) {
|
|
@@ -1648,6 +2191,10 @@ function componentKeyType(key) {
|
|
|
1648
2191
|
const colon = key.indexOf(":");
|
|
1649
2192
|
return colon > 0 ? key.slice(0, colon) : null;
|
|
1650
2193
|
}
|
|
2194
|
+
function componentKeyIdentifier(key) {
|
|
2195
|
+
const colon = key.indexOf(":");
|
|
2196
|
+
return colon >= 0 ? key.slice(colon + 1) : "";
|
|
2197
|
+
}
|
|
1651
2198
|
function isKnownProp(type, key) {
|
|
1652
2199
|
if (directiveProps.has(key) || key.startsWith("on"))
|
|
1653
2200
|
return true;
|
|
@@ -1657,16 +2204,55 @@ function isKnownProp(type, key) {
|
|
|
1657
2204
|
const propName = key.startsWith("$") ? key.slice(1) : key;
|
|
1658
2205
|
return propName in spec.props;
|
|
1659
2206
|
}
|
|
2207
|
+
function isLogicProp(key) {
|
|
2208
|
+
return directiveProps.has(key) || key.startsWith("$") || key.startsWith("on");
|
|
2209
|
+
}
|
|
2210
|
+
function collectLogicSources(value, path = "") {
|
|
2211
|
+
const sources = [];
|
|
2212
|
+
if (!value || typeof value !== "object")
|
|
2213
|
+
return sources;
|
|
2214
|
+
for (const [key, child] of Object.entries(value)) {
|
|
2215
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
2216
|
+
if (typeof child === "function") {
|
|
2217
|
+
sources.push({ path: childPath, source: child.toString() });
|
|
2218
|
+
continue;
|
|
2219
|
+
}
|
|
2220
|
+
if (typeof child === "string" && isLogicProp(key)) {
|
|
2221
|
+
sources.push({ path: childPath, source: child });
|
|
2222
|
+
continue;
|
|
2223
|
+
}
|
|
2224
|
+
sources.push(...collectLogicSources(child, childPath));
|
|
2225
|
+
}
|
|
2226
|
+
return sources;
|
|
2227
|
+
}
|
|
1660
2228
|
function walkComponents(value, warnings, usage, path = "") {
|
|
1661
2229
|
if (!value || typeof value !== "object")
|
|
1662
2230
|
return;
|
|
1663
2231
|
for (const [key, child] of Object.entries(value)) {
|
|
1664
2232
|
const childPath = path ? `${path}.${key}` : key;
|
|
2233
|
+
const isInvalidComponentKey = key.includes(":") && !componentKeyPattern.test(key);
|
|
2234
|
+
if (isInvalidComponentKey) {
|
|
2235
|
+
warnings.push({
|
|
2236
|
+
code: "invalid_component_key",
|
|
2237
|
+
message: `Invalid component key '${key}'. Use 'type:identifier' with a kebab-case type.`,
|
|
2238
|
+
path: childPath,
|
|
2239
|
+
value: key
|
|
2240
|
+
});
|
|
2241
|
+
}
|
|
1665
2242
|
const type = componentKeyType(key);
|
|
1666
2243
|
if (type) {
|
|
1667
2244
|
usage.add(type);
|
|
2245
|
+
const identifier = componentKeyIdentifier(key);
|
|
2246
|
+
if (reservedContextNames.has(identifier)) {
|
|
2247
|
+
warnings.push({
|
|
2248
|
+
code: "reserved_context_shadowing",
|
|
2249
|
+
message: `Component identifier '${identifier}' shadows a reserved expression context name.`,
|
|
2250
|
+
path: childPath,
|
|
2251
|
+
value: identifier
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
1668
2254
|
const spec = componentSpecByType2.get(type);
|
|
1669
|
-
if (!spec) {
|
|
2255
|
+
if (!spec && !isInvalidComponentKey) {
|
|
1670
2256
|
warnings.push({
|
|
1671
2257
|
code: "unknown_component",
|
|
1672
2258
|
message: `Unknown SlexKit component '${type}'.`,
|
|
@@ -1675,9 +2261,17 @@ function walkComponents(value, warnings, usage, path = "") {
|
|
|
1675
2261
|
});
|
|
1676
2262
|
}
|
|
1677
2263
|
if (child && typeof child === "object") {
|
|
1678
|
-
for (const propName of Object.
|
|
2264
|
+
for (const [propName, propValue] of Object.entries(child)) {
|
|
1679
2265
|
if (componentKeyType(propName))
|
|
1680
2266
|
continue;
|
|
2267
|
+
if (directiveProps.has(propName) && typeof propValue !== "string") {
|
|
2268
|
+
warnings.push({
|
|
2269
|
+
code: "invalid_directive_type",
|
|
2270
|
+
message: `Directive '${propName}' on component '${type}' must be a JavaScript expression string.`,
|
|
2271
|
+
path: `${childPath}.${propName}`,
|
|
2272
|
+
value: propName
|
|
2273
|
+
});
|
|
2274
|
+
}
|
|
1681
2275
|
if (!isKnownProp(type, propName)) {
|
|
1682
2276
|
warnings.push({
|
|
1683
2277
|
code: "unknown_prop",
|
|
@@ -1727,24 +2321,103 @@ function sourceWarnings(source, mode) {
|
|
|
1727
2321
|
}
|
|
1728
2322
|
return { warnings, stdlibUsage, apiUsage };
|
|
1729
2323
|
}
|
|
2324
|
+
function logicSourceWarnings(sources, mode) {
|
|
2325
|
+
const warnings = [];
|
|
2326
|
+
const stdlibUsage = new Set;
|
|
2327
|
+
const apiUsage = new Set;
|
|
2328
|
+
for (const logic of sources) {
|
|
2329
|
+
for (const name of collectMemberUsage(logic.source, "std")) {
|
|
2330
|
+
stdlibUsage.add(name);
|
|
2331
|
+
if (stdlibFunctions.has(name) || stdlibNamespaces.has(name))
|
|
2332
|
+
continue;
|
|
2333
|
+
warnings.push({
|
|
2334
|
+
code: "unknown_std_member",
|
|
2335
|
+
message: `Unknown SlexKit stdlib member '${name}'.`,
|
|
2336
|
+
path: logic.path,
|
|
2337
|
+
value: name
|
|
2338
|
+
});
|
|
2339
|
+
}
|
|
2340
|
+
for (const name of collectMemberUsage(logic.source, "api")) {
|
|
2341
|
+
apiUsage.add(name);
|
|
2342
|
+
if (apiMembers.has(name))
|
|
2343
|
+
continue;
|
|
2344
|
+
warnings.push({
|
|
2345
|
+
code: "unknown_api_member",
|
|
2346
|
+
message: `Unknown SlexKit runtime API member '${name}'.`,
|
|
2347
|
+
path: logic.path,
|
|
2348
|
+
value: name
|
|
2349
|
+
});
|
|
2350
|
+
}
|
|
2351
|
+
if (mode === "secure") {
|
|
2352
|
+
for (const capability of nativeSecureCapabilities) {
|
|
2353
|
+
if (!capability.pattern.test(logic.source))
|
|
2354
|
+
continue;
|
|
2355
|
+
warnings.push({
|
|
2356
|
+
code: "native_secure_capability",
|
|
2357
|
+
message: `Native '${capability.value}' is not supported in secure mode. Use policy-gated api.* instead.`,
|
|
2358
|
+
path: logic.path,
|
|
2359
|
+
value: capability.value
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
return {
|
|
2365
|
+
warnings,
|
|
2366
|
+
stdlibUsage: [...stdlibUsage].sort(),
|
|
2367
|
+
apiUsage: [...apiUsage].sort()
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
function envelopeWarnings(value, warnings) {
|
|
2371
|
+
if (!value || typeof value !== "object")
|
|
2372
|
+
return;
|
|
2373
|
+
const expression = value;
|
|
2374
|
+
if (expression.slex !== undefined && expression.slex !== SLEX_PROTOCOL_VERSION) {
|
|
2375
|
+
warnings.push({
|
|
2376
|
+
code: "unsupported_protocol",
|
|
2377
|
+
message: `Slex protocol marker '${String(expression.slex)}' does not match supported protocol '${SLEX_PROTOCOL_VERSION}'.`,
|
|
2378
|
+
path: "slex",
|
|
2379
|
+
value: String(expression.slex)
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
if (expression.g && typeof expression.g === "object") {
|
|
2383
|
+
for (const key of Object.keys(expression.g)) {
|
|
2384
|
+
if (!reservedContextNames.has(key))
|
|
2385
|
+
continue;
|
|
2386
|
+
warnings.push({
|
|
2387
|
+
code: "reserved_context_shadowing",
|
|
2388
|
+
message: `State key '${key}' shadows a reserved expression context name.`,
|
|
2389
|
+
path: `g.${key}`,
|
|
2390
|
+
value: key
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
1730
2395
|
function validateSlexSource(source, options = {}) {
|
|
1731
2396
|
const mode = options.mode ?? "trusted";
|
|
1732
2397
|
const parsed = parseSlexSource(source);
|
|
1733
2398
|
const usage = new Set;
|
|
1734
|
-
const { warnings, stdlibUsage, apiUsage } = sourceWarnings(source, mode);
|
|
1735
2399
|
if (!parsed.ok) {
|
|
2400
|
+
const { warnings: warnings2, stdlibUsage: stdlibUsage2, apiUsage: apiUsage2 } = sourceWarnings(source, mode);
|
|
1736
2401
|
return {
|
|
1737
2402
|
ok: false,
|
|
2403
|
+
schemaVersion: SLEX_SCHEMA_VERSION,
|
|
2404
|
+
protocolVersion: SLEX_PROTOCOL_VERSION,
|
|
2405
|
+
logicProfileVersion: SLEX_LOGIC_PROFILE_VERSION,
|
|
1738
2406
|
diagnostic: parsed.diagnostic,
|
|
1739
|
-
warnings,
|
|
2407
|
+
warnings: warnings2,
|
|
1740
2408
|
componentUsage: [],
|
|
1741
|
-
stdlibUsage,
|
|
1742
|
-
apiUsage
|
|
2409
|
+
stdlibUsage: stdlibUsage2,
|
|
2410
|
+
apiUsage: apiUsage2
|
|
1743
2411
|
};
|
|
1744
2412
|
}
|
|
2413
|
+
const { warnings, stdlibUsage, apiUsage } = logicSourceWarnings(collectLogicSources(parsed.value), mode);
|
|
2414
|
+
envelopeWarnings(parsed.value, warnings);
|
|
1745
2415
|
walkComponents(parsed.value, warnings, usage);
|
|
1746
2416
|
return {
|
|
1747
2417
|
ok: true,
|
|
2418
|
+
schemaVersion: SLEX_SCHEMA_VERSION,
|
|
2419
|
+
protocolVersion: SLEX_PROTOCOL_VERSION,
|
|
2420
|
+
logicProfileVersion: SLEX_LOGIC_PROFILE_VERSION,
|
|
1748
2421
|
value: parsed.value,
|
|
1749
2422
|
warnings,
|
|
1750
2423
|
componentUsage: [...usage].sort(),
|
|
@@ -1752,6 +2425,99 @@ function validateSlexSource(source, options = {}) {
|
|
|
1752
2425
|
apiUsage
|
|
1753
2426
|
};
|
|
1754
2427
|
}
|
|
2428
|
+
function normalizeExpectedWarning(warning) {
|
|
2429
|
+
return typeof warning === "string" ? { code: warning } : warning;
|
|
2430
|
+
}
|
|
2431
|
+
function warningMatches(actual, expected) {
|
|
2432
|
+
if (actual.code !== expected.code)
|
|
2433
|
+
return false;
|
|
2434
|
+
if (expected.path !== undefined && actual.path !== expected.path)
|
|
2435
|
+
return false;
|
|
2436
|
+
if (expected.value !== undefined && actual.value !== expected.value)
|
|
2437
|
+
return false;
|
|
2438
|
+
return true;
|
|
2439
|
+
}
|
|
2440
|
+
function warningLabel(warning) {
|
|
2441
|
+
return [warning.code, warning.path, warning.value].filter((item) => item !== undefined).join(" ");
|
|
2442
|
+
}
|
|
2443
|
+
function caseResult(fixture) {
|
|
2444
|
+
const result = validateSlexSource(fixture.source, { mode: fixture.mode });
|
|
2445
|
+
const errors = [];
|
|
2446
|
+
if (result.ok !== fixture.expected.ok) {
|
|
2447
|
+
errors.push(`Expected ok=${String(fixture.expected.ok)} but got ok=${String(result.ok)}.`);
|
|
2448
|
+
}
|
|
2449
|
+
const expectedWarnings = fixture.expected.warnings?.map(normalizeExpectedWarning);
|
|
2450
|
+
if (expectedWarnings) {
|
|
2451
|
+
for (const expected of expectedWarnings) {
|
|
2452
|
+
if (!result.warnings.some((warning) => warningMatches(warning, expected))) {
|
|
2453
|
+
errors.push(`Missing warning ${JSON.stringify(expected)}.`);
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
for (const actual of result.warnings) {
|
|
2457
|
+
if (!expectedWarnings.some((expected) => warningMatches(actual, expected))) {
|
|
2458
|
+
errors.push(`Unexpected warning ${warningLabel(actual)}.`);
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
if (fixture.expected.diagnostic) {
|
|
2463
|
+
if (result.ok) {
|
|
2464
|
+
errors.push(`Expected diagnostic '${fixture.expected.diagnostic}' but validation succeeded.`);
|
|
2465
|
+
} else if (result.diagnostic.code !== fixture.expected.diagnostic) {
|
|
2466
|
+
errors.push(`Expected diagnostic '${fixture.expected.diagnostic}' but got '${result.diagnostic.code}'.`);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
return {
|
|
2470
|
+
id: fixture.id,
|
|
2471
|
+
kind: fixture.kind,
|
|
2472
|
+
mode: fixture.mode,
|
|
2473
|
+
ok: errors.length === 0,
|
|
2474
|
+
expected: fixture.expected,
|
|
2475
|
+
actual: {
|
|
2476
|
+
ok: result.ok,
|
|
2477
|
+
warnings: result.warnings,
|
|
2478
|
+
diagnostic: result.ok ? undefined : result.diagnostic
|
|
2479
|
+
},
|
|
2480
|
+
errors
|
|
2481
|
+
};
|
|
2482
|
+
}
|
|
2483
|
+
function conformanceFixtures(packageVersion) {
|
|
2484
|
+
const artifact = createStandardArtifacts(packageVersion).artifacts["slex-conformance.json"];
|
|
2485
|
+
return artifact.fixtures;
|
|
2486
|
+
}
|
|
2487
|
+
function runSlexConformance(options = {}) {
|
|
2488
|
+
const packageVersion = options.packageVersion ?? SLEXKIT_VERSION;
|
|
2489
|
+
const fixtures = conformanceFixtures(packageVersion);
|
|
2490
|
+
const selectedFixtures = options.fixtureId ? fixtures.filter((fixture) => fixture.id === options.fixtureId) : fixtures;
|
|
2491
|
+
if (options.fixtureId && selectedFixtures.length === 0) {
|
|
2492
|
+
return {
|
|
2493
|
+
ok: false,
|
|
2494
|
+
packageVersion,
|
|
2495
|
+
schemaVersion: SLEX_SCHEMA_VERSION,
|
|
2496
|
+
protocolVersion: SLEX_PROTOCOL_VERSION,
|
|
2497
|
+
logicProfileVersion: SLEX_LOGIC_PROFILE_VERSION,
|
|
2498
|
+
total: 0,
|
|
2499
|
+
passed: 0,
|
|
2500
|
+
failed: 0,
|
|
2501
|
+
fixtureId: options.fixtureId,
|
|
2502
|
+
cases: [],
|
|
2503
|
+
error: `Unknown conformance fixture '${options.fixtureId}'.`
|
|
2504
|
+
};
|
|
2505
|
+
}
|
|
2506
|
+
const cases = selectedFixtures.map(caseResult);
|
|
2507
|
+
const failed = cases.filter((item) => !item.ok).length;
|
|
2508
|
+
return {
|
|
2509
|
+
ok: failed === 0,
|
|
2510
|
+
packageVersion,
|
|
2511
|
+
schemaVersion: SLEX_SCHEMA_VERSION,
|
|
2512
|
+
protocolVersion: SLEX_PROTOCOL_VERSION,
|
|
2513
|
+
logicProfileVersion: SLEX_LOGIC_PROFILE_VERSION,
|
|
2514
|
+
total: cases.length,
|
|
2515
|
+
passed: cases.length - failed,
|
|
2516
|
+
failed,
|
|
2517
|
+
fixtureId: options.fixtureId,
|
|
2518
|
+
cases
|
|
2519
|
+
};
|
|
2520
|
+
}
|
|
1755
2521
|
var defaultRuntimeUrl = typeof document !== "undefined" && typeof document.currentScript === "object" && document.currentScript && "src" in document.currentScript ? String(document.currentScript.src || "") : undefined;
|
|
1756
2522
|
function setSlexKitRuntimeUrl(url) {
|
|
1757
2523
|
defaultRuntimeUrl = url || undefined;
|
|
@@ -1770,13 +2536,26 @@ var canvasSnapshot = {
|
|
|
1770
2536
|
};
|
|
1771
2537
|
setSlexKitRuntimeUrl(import.meta.url);
|
|
1772
2538
|
var parseSlexSourceApi = parseSlexSource;
|
|
2539
|
+
var runSlexConformanceApi = runSlexConformance;
|
|
1773
2540
|
var validateSlexSourceApi = validateSlexSource;
|
|
1774
2541
|
|
|
1775
2542
|
// src/index.ts
|
|
1776
2543
|
var dataBase = new URL("./data/", import.meta.url);
|
|
2544
|
+
var standardArtifactNames = [
|
|
2545
|
+
"slex-expression.schema.json",
|
|
2546
|
+
"slex-component-catalog.json",
|
|
2547
|
+
"slex-logic-profile.json",
|
|
2548
|
+
"slex-capabilities.catalog.json",
|
|
2549
|
+
"slex-conformance.json",
|
|
2550
|
+
"slex-standard-manifest.json"
|
|
2551
|
+
];
|
|
2552
|
+
var standardArtifactNameSet = new Set(standardArtifactNames);
|
|
1777
2553
|
async function readDataFile(name) {
|
|
1778
2554
|
return readFile(new URL(name, dataBase), "utf-8");
|
|
1779
2555
|
}
|
|
2556
|
+
async function readStandardArtifact(filename) {
|
|
2557
|
+
return JSON.parse(await readDataFile(`standard/${filename}`));
|
|
2558
|
+
}
|
|
1780
2559
|
var manifest = JSON.parse(await readDataFile("slexkit-ai-manifest.json"));
|
|
1781
2560
|
function stringArg(args, key) {
|
|
1782
2561
|
const value = args[key];
|
|
@@ -1937,9 +2716,42 @@ var tools = [
|
|
|
1937
2716
|
group: { type: "string", description: "Optional group filter: Guides, Components, Runtime, ToolHost, Security, Packages, Icons." },
|
|
1938
2717
|
slug: { type: "string", description: "Optional page id or slug to fetch, such as components/card or card." },
|
|
1939
2718
|
url: { type: "string", description: "Optional href or raw .md URL to fetch." },
|
|
1940
|
-
includeCapabilities: { type: "boolean", description: "When true, include expression context, stdlib, and api capability summaries." }
|
|
2719
|
+
includeCapabilities: { type: "boolean", description: "When true, include expression context, stdlib, and api capability summaries." },
|
|
2720
|
+
conformanceReport: {
|
|
2721
|
+
type: "boolean",
|
|
2722
|
+
description: "When true, run SlexKit's bundled standard conformance fixtures and return a structured report."
|
|
2723
|
+
},
|
|
2724
|
+
conformanceFixture: {
|
|
2725
|
+
type: "string",
|
|
2726
|
+
description: "Optional fixture id to run when conformanceReport is true."
|
|
2727
|
+
},
|
|
2728
|
+
standardArtifact: {
|
|
2729
|
+
type: "string",
|
|
2730
|
+
enum: standardArtifactNames,
|
|
2731
|
+
description: "Optional standard artifact JSON to fetch, such as slex-standard-manifest.json, slex-component-catalog.json, or slex-logic-profile.json."
|
|
2732
|
+
}
|
|
1941
2733
|
}),
|
|
1942
|
-
handler(args) {
|
|
2734
|
+
async handler(args) {
|
|
2735
|
+
if (booleanArg(args, "conformanceReport")) {
|
|
2736
|
+
return {
|
|
2737
|
+
version: manifest.version,
|
|
2738
|
+
conformanceReport: runSlexConformanceApi({ fixtureId: optionalStringArg(args, "conformanceFixture") })
|
|
2739
|
+
};
|
|
2740
|
+
}
|
|
2741
|
+
const standardArtifact = optionalStringArg(args, "standardArtifact");
|
|
2742
|
+
if (standardArtifact) {
|
|
2743
|
+
if (!standardArtifactNameSet.has(standardArtifact)) {
|
|
2744
|
+
return { error: "standard_artifact_not_found", standardArtifact, available: standardArtifactNames };
|
|
2745
|
+
}
|
|
2746
|
+
return {
|
|
2747
|
+
version: manifest.version,
|
|
2748
|
+
standardArtifact: {
|
|
2749
|
+
filename: standardArtifact,
|
|
2750
|
+
metadata: manifest.standardArtifacts?.[standardArtifact],
|
|
2751
|
+
content: await readStandardArtifact(standardArtifact)
|
|
2752
|
+
}
|
|
2753
|
+
};
|
|
2754
|
+
}
|
|
1943
2755
|
const capabilities = booleanArg(args, "includeCapabilities") ? {
|
|
1944
2756
|
expressionContext: manifest.expressionContext,
|
|
1945
2757
|
stdlib: manifest.stdlib,
|
|
@@ -1947,8 +2759,8 @@ var tools = [
|
|
|
1947
2759
|
} : undefined;
|
|
1948
2760
|
const page = findPage(args);
|
|
1949
2761
|
if (page)
|
|
1950
|
-
return { version: manifest.version, page, capabilities };
|
|
1951
|
-
return { version: manifest.version, pages: searchPages(args), capabilities };
|
|
2762
|
+
return { version: manifest.version, page, capabilities, standardArtifacts: manifest.standardArtifacts };
|
|
2763
|
+
return { version: manifest.version, pages: searchPages(args), capabilities, standardArtifacts: manifest.standardArtifacts };
|
|
1952
2764
|
}
|
|
1953
2765
|
},
|
|
1954
2766
|
{
|
|
@@ -2060,4 +2872,4 @@ rl.on("line", (line) => {
|
|
|
2060
2872
|
})();
|
|
2061
2873
|
});
|
|
2062
2874
|
|
|
2063
|
-
//# debugId=
|
|
2875
|
+
//# debugId=E327C2B1CABE15F364756E2164756E21
|