@mindfiredigital/ignix-lite-engine 1.1.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.
Files changed (91) hide show
  1. package/.turbo/turbo-build.log +22 -0
  2. package/CHANGELOG.md +7 -0
  3. package/LICENSE +21 -0
  4. package/README.md +283 -0
  5. package/dist/index.d.ts +171 -0
  6. package/dist/index.js +2540 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/manifests/accordion.json +61 -0
  9. package/dist/manifests/alert.json +69 -0
  10. package/dist/manifests/avatar.json +75 -0
  11. package/dist/manifests/badge.json +74 -0
  12. package/dist/manifests/breadcrumb.json +87 -0
  13. package/dist/manifests/button.json +85 -0
  14. package/dist/manifests/card.json +91 -0
  15. package/dist/manifests/checkbox.json +122 -0
  16. package/dist/manifests/codeblock.json +63 -0
  17. package/dist/manifests/combobox.json +33 -0
  18. package/dist/manifests/dialog.json +64 -0
  19. package/dist/manifests/divider.json +47 -0
  20. package/dist/manifests/dropdown.json +105 -0
  21. package/dist/manifests/form.json +81 -0
  22. package/dist/manifests/grid.json +143 -0
  23. package/dist/manifests/input.json +99 -0
  24. package/dist/manifests/meter.json +103 -0
  25. package/dist/manifests/navigation.json +70 -0
  26. package/dist/manifests/progress.json +88 -0
  27. package/dist/manifests/radio.json +121 -0
  28. package/dist/manifests/select.json +109 -0
  29. package/dist/manifests/skeleton.json +101 -0
  30. package/dist/manifests/tab.json +88 -0
  31. package/dist/manifests/table.json +92 -0
  32. package/dist/manifests/textarea.json +117 -0
  33. package/dist/manifests/toast.json +157 -0
  34. package/dist/manifests/tooltip.json +115 -0
  35. package/dist/vector-index.json +14015 -0
  36. package/package.json +33 -0
  37. package/src/global.d.ts +3 -0
  38. package/src/index.ts +14 -0
  39. package/src/manifests/accordion.json +61 -0
  40. package/src/manifests/alert.json +69 -0
  41. package/src/manifests/avatar.json +75 -0
  42. package/src/manifests/badge.json +74 -0
  43. package/src/manifests/breadcrumb.json +87 -0
  44. package/src/manifests/button.json +85 -0
  45. package/src/manifests/card.json +91 -0
  46. package/src/manifests/checkbox.json +122 -0
  47. package/src/manifests/codeblock.json +63 -0
  48. package/src/manifests/combobox.json +33 -0
  49. package/src/manifests/dialog.json +64 -0
  50. package/src/manifests/divider.json +47 -0
  51. package/src/manifests/dropdown.json +105 -0
  52. package/src/manifests/form.json +81 -0
  53. package/src/manifests/grid.json +143 -0
  54. package/src/manifests/index.ts +49 -0
  55. package/src/manifests/input.json +99 -0
  56. package/src/manifests/meter.json +103 -0
  57. package/src/manifests/navigation.json +70 -0
  58. package/src/manifests/progress.json +88 -0
  59. package/src/manifests/radio.json +121 -0
  60. package/src/manifests/select.json +109 -0
  61. package/src/manifests/skeleton.json +101 -0
  62. package/src/manifests/tab.json +88 -0
  63. package/src/manifests/table.json +92 -0
  64. package/src/manifests/textarea.json +117 -0
  65. package/src/manifests/toast.json +157 -0
  66. package/src/manifests/tooltip.json +115 -0
  67. package/src/tools/build-index.ts +43 -0
  68. package/src/tools/check-a11y.ts +96 -0
  69. package/src/tools/embedder.ts +18 -0
  70. package/src/tools/generate-theme.ts +42 -0
  71. package/src/tools/get-emmet.ts +64 -0
  72. package/src/tools/get-manifests.ts +55 -0
  73. package/src/tools/handoff.ts +302 -0
  74. package/src/tools/intent-engine.ts +215 -0
  75. package/src/tools/list-components.ts +20 -0
  76. package/src/tools/preview.ts +186 -0
  77. package/src/tools/search-index.ts +82 -0
  78. package/src/tools/theme-palette.ts +65 -0
  79. package/src/tools/theme-tokens.ts +176 -0
  80. package/src/tools/token-counter.ts +59 -0
  81. package/src/tools/validator.ts +353 -0
  82. package/src/types.ts +63 -0
  83. package/src/utils/a11y-rules.ts +873 -0
  84. package/src/utils/a11y-types.ts +15 -0
  85. package/src/utils/cosine.ts +15 -0
  86. package/src/utils/emmet-helpers.ts +283 -0
  87. package/src/utils/intent-helpers.ts +66 -0
  88. package/src/utils/intent-parser.ts +175 -0
  89. package/src/utils/tokenizer.ts +7 -0
  90. package/tsconfig.json +17 -0
  91. package/tsup.config.ts +10 -0
@@ -0,0 +1,176 @@
1
+ import { resolveColor } from './theme-palette.js'
2
+
3
+ export interface ThemeTokens {
4
+ primary: string
5
+ primaryHover: string
6
+ primaryContrast: string
7
+ primaryBg: string
8
+
9
+ danger: string
10
+ dangerText: string
11
+ dangerBg: string
12
+
13
+ warning: string
14
+ warningBg: string
15
+
16
+ success: string
17
+ successBg: string
18
+
19
+ infoBg: string
20
+ neutral: string
21
+ divider: string
22
+
23
+ surface: string
24
+ surfaceRaised: string
25
+
26
+ text: string
27
+ textMuted: string
28
+ border: string
29
+
30
+ radius: string
31
+ radiusLg: string
32
+ badgeRadius: string
33
+
34
+ skeletonBg: string
35
+ skeletonShimmer: string
36
+
37
+ gradientStart: string
38
+ gradientEnd: string
39
+ gradientBgEnd: string
40
+
41
+ isDark: boolean
42
+ resolvedPrimary: string
43
+ resolvedContrast: string
44
+ }
45
+
46
+ export function resolveTokens(query: string): ThemeTokens {
47
+ const { primary, contrast } = resolveColor(query)
48
+
49
+ const isDark =
50
+ query.includes('dark') ||
51
+ query.includes('night') ||
52
+ query.includes('midnight') ||
53
+ query.includes('cyberpunk') ||
54
+ query.includes('dim')
55
+
56
+ const sharp =
57
+ query.includes('sharp') ||
58
+ query.includes('flat') ||
59
+ query.includes('square')
60
+ const round =
61
+ query.includes('round') || query.includes('pill') || query.includes('soft')
62
+
63
+ return {
64
+ resolvedPrimary: primary,
65
+ resolvedContrast: contrast,
66
+ isDark,
67
+
68
+ primary,
69
+ primaryHover: isDark
70
+ ? 'color-mix(in srgb, var(--ix-primary) 75%, white)'
71
+ : 'color-mix(in srgb, var(--ix-primary) 85%, black)',
72
+ primaryContrast: isDark
73
+ ? 'color-mix(in srgb, var(--ix-primary) 40%, white)'
74
+ : contrast,
75
+ primaryBg: isDark
76
+ ? 'color-mix(in srgb, var(--ix-primary) 20%, #0f172a)'
77
+ : 'color-mix(in srgb, var(--ix-primary) 15%, white)',
78
+
79
+ danger: '#ef4444',
80
+ dangerText: isDark ? '#f87171' : '#ef4444',
81
+ dangerBg: isDark ? '#3f1d1d' : '#fee2e2',
82
+
83
+ warning: isDark ? '#fbbf24' : '#f59e0b',
84
+ warningBg: isDark ? '#3f2e05' : '#fef3c7',
85
+
86
+ success: '#22c55e',
87
+ successBg: isDark ? '#052e16' : '#dcfce7',
88
+
89
+ infoBg: isDark ? '#1e293b' : '#f3f4f6',
90
+ neutral: isDark ? '#9ca3af' : '#6b7280',
91
+ divider: isDark ? '#475569' : '#9ca3af',
92
+
93
+ surface: isDark ? '#0f172a' : '#f9fafb',
94
+ surfaceRaised: isDark ? '#1e293b' : '#ffffff',
95
+
96
+ text: isDark ? '#f9fafb' : '#111827',
97
+ textMuted: isDark ? '#94a3b8' : '#6b7280',
98
+ border: isDark ? '#334155' : '#e5e7eb',
99
+
100
+ radius: sharp ? '0px' : round ? '0.75rem' : '0.375rem',
101
+ radiusLg: sharp ? '0px' : round ? '1rem' : '0.5rem',
102
+ badgeRadius: '999px',
103
+
104
+ skeletonBg: isDark ? '#1e293b' : '#e5e7eb',
105
+ skeletonShimmer: isDark ? 'rgba(255,255,255,0.1)' : 'rgba(255,255,255,0.6)',
106
+
107
+ gradientStart: primary,
108
+ gradientEnd: '#fffbd5',
109
+ gradientBgEnd: isDark ? '#aba67a' : '#d7d4bc'
110
+ }
111
+ }
112
+
113
+ export function buildCss(t: ThemeTokens): string {
114
+ return `:root {
115
+ --ix-primary: ${t.primary};
116
+ --ix-primary-hover: ${t.primaryHover};
117
+ --ix-primary-contrast: ${t.primaryContrast};
118
+
119
+ --ix-danger: ${t.danger};
120
+ --ix-danger-text: ${t.dangerText};
121
+
122
+ --ix-warning: ${t.warning};
123
+ --ix-success: ${t.success};
124
+ --ix-neutral: ${t.neutral};
125
+ --ix-ghost: transparent;
126
+
127
+ --ix-primary-bg: ${t.primaryBg};
128
+ --ix-danger-bg: ${t.dangerBg};
129
+ --ix-warning-bg: ${t.warningBg};
130
+ --ix-success-bg: ${t.successBg};
131
+ --ix-info-bg: ${t.infoBg};
132
+
133
+ --ix-on-danger: #ffffff;
134
+ --ix-on-warning: #000000;
135
+ --ix-on-success: #ffffff;
136
+ --ix-on-primary: ${t.resolvedContrast};
137
+
138
+ --ix-divider: ${t.divider};
139
+
140
+ --ix-surface: ${t.surface};
141
+ --ix-surface-raised: ${t.surfaceRaised};
142
+
143
+ --ix-text: ${t.text};
144
+ --ix-text-muted: ${t.textMuted};
145
+
146
+ --ix-border: ${t.border};
147
+ --ix-focus: 2px solid var(--ix-primary);
148
+
149
+ --ix-font: 'Segoe UI Variable', 'Segoe UI', 'Roboto', 'Noto Sans', system-ui, sans-serif;
150
+ --ix-font-mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
151
+
152
+ --ix-size-xs: 0.75rem;
153
+ --ix-size-sm: 0.875rem;
154
+ --ix-size-md: 1rem;
155
+ --ix-size-lg: 1.125rem;
156
+ --ix-size-xl: 1.25rem;
157
+
158
+ --ix-line-height: 1.5;
159
+
160
+ --ix-space-sm: 0.5rem;
161
+ --ix-space-md: 1rem;
162
+ --ix-space-lg: 1.5rem;
163
+
164
+ --ix-radius: ${t.radius};
165
+ --ix-radius-lg: ${t.radiusLg};
166
+ --ix-badge-radius: ${t.badgeRadius};
167
+
168
+ --ix-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
169
+
170
+ --ix-gradient: linear-gradient(to right, ${t.gradientStart}, ${t.gradientEnd});
171
+ --ix-gradient-bg: linear-gradient(to right, ${t.gradientStart}, ${t.gradientBgEnd});
172
+
173
+ --ix-skeleton-bg: ${t.skeletonBg};
174
+ --ix-skeleton-shimmer: ${t.skeletonShimmer};
175
+ }`
176
+ }
@@ -0,0 +1,59 @@
1
+ import type { MCPResponse } from '../types.js'
2
+
3
+ export type CallRecord = {
4
+ tool: string
5
+ tokens_used: number
6
+ timestamp: number
7
+ }
8
+
9
+ let totalTokensUsed = 0
10
+ const calls: CallRecord[] = []
11
+ const sessionId = Math.random().toString(36).substring(2, 15)
12
+
13
+ const MAX_CALL_HISTORY = 1000
14
+
15
+ export function recordCall(toolName: string, tokensUsed: number): void {
16
+ const parsedTokens = Number(tokensUsed)
17
+ const validTokens =
18
+ isNaN(parsedTokens) || parsedTokens < 0 || !isFinite(parsedTokens)
19
+ ? 0
20
+ : parsedTokens
21
+
22
+ if (calls.length >= MAX_CALL_HISTORY) {
23
+ calls.shift()
24
+ }
25
+ calls.push({
26
+ tool: toolName,
27
+ tokens_used: validTokens,
28
+ timestamp: Date.now()
29
+ })
30
+ totalTokensUsed += validTokens
31
+ }
32
+
33
+ export function getTokenSummary(
34
+ args: { context_window?: number } = {}
35
+ ): MCPResponse {
36
+ const contextWindowInput = args.context_window || 128000
37
+ const parsedWindow = Number(contextWindowInput)
38
+ const contextWindow =
39
+ isNaN(parsedWindow) || parsedWindow <= 0 || !isFinite(parsedWindow)
40
+ ? 128000
41
+ : parsedWindow
42
+
43
+ const pct = Number(((totalTokensUsed / contextWindow) * 100).toFixed(4))
44
+
45
+ return {
46
+ content: [
47
+ {
48
+ type: 'text',
49
+ text: JSON.stringify({
50
+ session_id: sessionId,
51
+ calls,
52
+ total_tokens_used: totalTokensUsed,
53
+ estimated_context_pct: pct,
54
+ tokens_used: 20
55
+ })
56
+ }
57
+ ]
58
+ }
59
+ }
@@ -0,0 +1,353 @@
1
+ import { parse } from 'node-html-parser'
2
+ import { manifests } from '../manifests/index.js'
3
+ import type { ManifestSlot } from '../types.js'
4
+
5
+ type ErrorType =
6
+ | 'UNKNOWN_ATTRIBUTE'
7
+ | 'INVALID_VALUE'
8
+ | 'FORBIDDEN_CLASS'
9
+ | 'MISSING_REQUIRED'
10
+ | 'WRONG_ELEMENT'
11
+ | 'PROP_EXPLOSION'
12
+ | 'JS_ON_CSS_COMPONENT'
13
+ | 'MISSING_SLOT'
14
+
15
+ export type ValidationError = {
16
+ element: string
17
+ prop: string
18
+ type: ErrorType
19
+ message: string
20
+ suggestion?: string
21
+ valid_values?: string[]
22
+ fix: string
23
+ confidence: number
24
+ line: number
25
+ }
26
+
27
+ function getLineNumber(html: string, index: number): number {
28
+ if (index === undefined || index === null) return 1
29
+ return html.substring(0, index).split('\n').length
30
+ }
31
+
32
+ export function validateHtml(html: string): { valid: boolean, score: number, errors: ValidationError[] } {
33
+ const root = parse(html)
34
+ const errors: ValidationError[] = []
35
+ const elements = root.querySelectorAll('*')
36
+
37
+ const allowedWrappers = [
38
+ 'label',
39
+ 'span',
40
+ 'p',
41
+ 'img',
42
+ 'small',
43
+ 'h1',
44
+ 'h2',
45
+ 'h3',
46
+ 'a',
47
+ 'button',
48
+ 'ul',
49
+ 'li',
50
+ 'thead',
51
+ 'tbody',
52
+ 'tr',
53
+ 'td',
54
+ 'th',
55
+ 'summary',
56
+ 'strong',
57
+ 'code',
58
+ 'em',
59
+ 'b',
60
+ 'i',
61
+ 'time'
62
+ ]
63
+
64
+ const nativeAttributes = new Set([
65
+ 'id',
66
+ 'role',
67
+ 'slot',
68
+ 'tabindex',
69
+ 'aria-label',
70
+ 'aria-live',
71
+ 'aria-hidden',
72
+ 'aria-expanded',
73
+ 'aria-selected',
74
+ 'aria-current',
75
+ 'aria-sort',
76
+ 'aria-invalid',
77
+ 'aria-describedby',
78
+ 'aria-labelledby',
79
+ 'aria-haspopup',
80
+ 'aria-busy',
81
+ 'disabled',
82
+ 'required',
83
+ 'checked',
84
+ 'multiple',
85
+ 'readonly',
86
+ 'type',
87
+ 'value',
88
+ 'placeholder',
89
+ 'name',
90
+ 'autocomplete',
91
+ 'min',
92
+ 'max',
93
+ 'low',
94
+ 'high',
95
+ 'optimum',
96
+ 'rows',
97
+ 'open',
98
+ 'hidden',
99
+ 'href',
100
+ 'content',
101
+ 'is',
102
+ 'src',
103
+ 'alt',
104
+ 'data-intent',
105
+ 'data-position',
106
+ 'data-variant',
107
+ 'data-shape',
108
+ 'data-sortable',
109
+ 'data-open',
110
+ 'data-lines',
111
+ 'data-grid',
112
+ 'data-gap',
113
+ 'data-align',
114
+ 'data-justify',
115
+ 'data-col',
116
+ 'data-row',
117
+ 'data-dense',
118
+ 'data-ix-manifest'
119
+ ])
120
+
121
+ for (const el of elements) {
122
+ const tag = el.tagName.toLowerCase()
123
+ const attrs = el.attributes
124
+ const startIdx = el.range ? el.range[0] : 0
125
+ const line = getLineNumber(html, startIdx)
126
+
127
+ const dataIxManifest = el.getAttribute('data-ix-manifest')
128
+ let manifestKey = dataIxManifest || (tag.startsWith('ix-') ? tag.slice(3) : tag)
129
+
130
+ if (tag === 'table' && attrs.is === 'ix-table') {
131
+ manifestKey = 'table'
132
+ }
133
+
134
+ if (tag === 'nav') {
135
+ manifestKey = 'navigation'
136
+ }
137
+
138
+ if (tag === 'article' && el.querySelector('[slot]')) {
139
+ manifestKey = 'card'
140
+ }
141
+
142
+ if (tag === 'aside') {
143
+ manifestKey = 'alert'
144
+ }
145
+
146
+ if (tag === 'details') {
147
+ manifestKey = 'accordion'
148
+ }
149
+
150
+ if (tag === 'hr') {
151
+ manifestKey = 'divider'
152
+ }
153
+
154
+ let manifest = manifests[manifestKey]
155
+
156
+ if (!manifest) {
157
+ if (tag === 'mark') {
158
+ manifest = manifests.badge
159
+ }
160
+ if (tag === 'span' && attrs.role === 'status') {
161
+ manifest = manifests.badge
162
+ }
163
+ }
164
+
165
+ if (!manifest && !allowedWrappers.includes(tag)) {
166
+ errors.push({
167
+ element: tag,
168
+ prop: '',
169
+ type: 'WRONG_ELEMENT',
170
+ message: `<${tag}> is not a valid ignix-lite component`,
171
+ fix: `<button>Fix me</button>`,
172
+ confidence: 0.7,
173
+ line
174
+ })
175
+ continue
176
+ }
177
+
178
+ if (!manifest) continue
179
+
180
+ const elementName = tag.startsWith('ix-') ? tag : manifest.element || tag
181
+
182
+ if (manifest.required_wrapper) {
183
+ const parent = el.parentNode as unknown as { tagName?: string }
184
+ if (!parent || parent.tagName?.toLowerCase() !== manifest.required_wrapper) {
185
+ errors.push({
186
+ element: tag,
187
+ prop: 'wrapper',
188
+ type: 'MISSING_REQUIRED',
189
+ message: `<${tag}> must be inside <${manifest.required_wrapper}>`,
190
+ fix: `<${manifest.required_wrapper}><${elementName}></${elementName}></${manifest.required_wrapper}>`,
191
+ confidence: 0.95,
192
+ line
193
+ })
194
+ }
195
+ }
196
+
197
+ if (tag === 'span' && manifest.component === 'badge' && attrs.role !== 'status') {
198
+ errors.push({
199
+ element: tag,
200
+ prop: 'role',
201
+ type: 'INVALID_VALUE',
202
+ message: 'span badge must have role=status',
203
+ fix: `<span role="status">${el.innerText}</span>`,
204
+ confidence: 0.9,
205
+ line
206
+ })
207
+ }
208
+
209
+ if ('class' in attrs) {
210
+ errors.push({
211
+ element: tag,
212
+ prop: 'class',
213
+ type: 'FORBIDDEN_CLASS',
214
+ message: 'class attribute not allowed',
215
+ fix: `<${elementName}>${el.innerText}</${elementName}>`,
216
+ confidence: 0.99,
217
+ line
218
+ })
219
+ }
220
+
221
+ const nonFreeAttrs = Object.keys(attrs).filter(
222
+ (key) => key !== 'id' && key !== 'role' && !key.startsWith('aria-')
223
+ )
224
+ if (nonFreeAttrs.length > 4) {
225
+ errors.push({
226
+ element: tag,
227
+ prop: 'multiple',
228
+ type: 'PROP_EXPLOSION',
229
+ message: 'Too many props',
230
+ fix: `<${elementName}></${elementName}>`,
231
+ confidence: 0.85,
232
+ line
233
+ })
234
+ }
235
+
236
+ for (const attr of Object.keys(attrs)) {
237
+ if (attr.startsWith('on')) {
238
+ errors.push({
239
+ element: tag,
240
+ prop: attr,
241
+ type: 'JS_ON_CSS_COMPONENT',
242
+ message: 'JS handlers forbidden',
243
+ fix: `<${elementName}></${elementName}>`,
244
+ confidence: 0.95,
245
+ line
246
+ })
247
+ }
248
+ }
249
+
250
+ for (const attr of Object.keys(attrs)) {
251
+ if (manifest.forbidden_props?.includes(attr)) {
252
+ errors.push({
253
+ element: tag,
254
+ prop: attr,
255
+ type: 'UNKNOWN_ATTRIBUTE',
256
+ message: `'${attr}' forbidden`,
257
+ fix: `<${elementName}></${elementName}>`,
258
+ confidence: 0.98,
259
+ line
260
+ })
261
+ continue
262
+ }
263
+
264
+ if (!manifest.props?.[attr] && !nativeAttributes.has(attr)) {
265
+ errors.push({
266
+ element: tag,
267
+ prop: attr,
268
+ type: 'UNKNOWN_ATTRIBUTE',
269
+ message: `'${attr}' invalid`,
270
+ fix: `<${elementName}></${elementName}>`,
271
+ confidence: 0.95,
272
+ line
273
+ })
274
+ }
275
+ }
276
+
277
+ for (const attr of Object.keys(attrs)) {
278
+ const def = manifest.props?.[attr]
279
+ if (def?.values) {
280
+ const value = attrs[attr]
281
+ if (!def.values.includes(value)) {
282
+ errors.push({
283
+ element: tag,
284
+ prop: attr,
285
+ type: 'INVALID_VALUE',
286
+ message: `'${value}' invalid`,
287
+ valid_values: def.values,
288
+ fix: `<${elementName} ${attr}="${def.values[0]}"></${elementName}>`,
289
+ confidence: 0.97,
290
+ line
291
+ })
292
+ }
293
+ }
294
+ }
295
+
296
+ for (const req of manifest.required_props || []) {
297
+ if (!(req in attrs)) {
298
+ errors.push({
299
+ element: tag,
300
+ prop: req,
301
+ type: 'MISSING_REQUIRED',
302
+ message: `Missing ${req}`,
303
+ fix: `<${elementName} ${req}=""></${elementName}>`,
304
+ confidence: 0.9,
305
+ line
306
+ })
307
+ }
308
+ }
309
+
310
+ const slots = manifest.slots ?? {}
311
+ for (const [slotName, slotDefVal] of Object.entries(slots)) {
312
+ const slotDef = slotDefVal as ManifestSlot
313
+ const child = el.querySelector(`[slot="${slotName}"]`)
314
+
315
+ if (slotDef.required && !child) {
316
+ errors.push({
317
+ element: tag,
318
+ prop: slotName,
319
+ type: 'MISSING_SLOT',
320
+ message: `Missing slot ${slotName}`,
321
+ fix: `<${elementName}><span slot="${slotName}"></span></${elementName}>`,
322
+ confidence: 0.95,
323
+ line
324
+ })
325
+ }
326
+
327
+ if (child && slotDef.element) {
328
+ const childTag = child.tagName.toLowerCase()
329
+ if (!slotDef.element.includes(childTag)) {
330
+ errors.push({
331
+ element: childTag,
332
+ prop: slotName,
333
+ type: 'INVALID_VALUE',
334
+ message: `${childTag} invalid for slot ${slotName}`,
335
+ valid_values: slotDef.element,
336
+ fix: `<${slotDef.element[0]} slot="${slotName}"></${slotDef.element[0]}>`,
337
+ confidence: 0.95,
338
+ line
339
+ })
340
+ }
341
+ }
342
+ }
343
+ }
344
+
345
+ const valid = errors.length === 0
346
+ const score = valid ? 100 : Math.max(0, 100 - errors.length * 10)
347
+
348
+ return {
349
+ valid,
350
+ score,
351
+ errors
352
+ }
353
+ }
package/src/types.ts ADDED
@@ -0,0 +1,63 @@
1
+ export type ToolName =
2
+ | 'list_components'
3
+ | 'get_manifest'
4
+ | 'get_emmet'
5
+ | 'validate'
6
+
7
+ export type ToolRequest = {
8
+ params: {
9
+ name: ToolName
10
+
11
+ arguments?: unknown
12
+ }
13
+ }
14
+
15
+ export type MCPResponse = {
16
+ content: {
17
+ type: 'text'
18
+ text: string
19
+
20
+ }[]
21
+ }
22
+
23
+ export type ManifestProp = {
24
+ type: string
25
+ values?: string[]
26
+ default?: string | boolean
27
+ native?: boolean
28
+ agent_hint?: string
29
+ }
30
+
31
+ export type ManifestSlot = {
32
+ required: boolean
33
+ element: string[]
34
+ agent_hint?: string
35
+ }
36
+
37
+ export type ManifestExample = {
38
+ label: string
39
+ emmet: string
40
+ html: string
41
+ }
42
+
43
+ export type Manifest = {
44
+ component: string
45
+ version: string
46
+ description: string
47
+ element: string
48
+ emmet: string
49
+ tokens: number
50
+ props: Record<string, ManifestProp>
51
+ slots?: Record<string, ManifestSlot>
52
+ states?: string[]
53
+ forbidden_props?: string[]
54
+ required_props?: string[]
55
+ required_slots?: string[]
56
+ required_wrapper?: string
57
+ methods?: string[]
58
+ do?: string[]
59
+ dont?: string[]
60
+ examples?: ManifestExample[]
61
+ extends?: string
62
+
63
+ }