mcp-probe-kit 3.0.14 → 3.0.16
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 +17 -11
- package/build/lib/__tests__/gitnexus-bridge.unit.test.js +9 -1
- package/build/lib/gitnexus-bridge.d.ts +1 -0
- package/build/lib/gitnexus-bridge.js +29 -1
- package/build/lib/skill-bridge.d.ts +31 -0
- package/build/lib/skill-bridge.js +100 -0
- package/build/resources/ui-ux-data/charts.json +302 -0
- package/build/resources/ui-ux-data/colors.json +1058 -0
- package/build/resources/ui-ux-data/icons.json +1102 -0
- package/build/resources/ui-ux-data/landing.json +262 -0
- package/build/resources/ui-ux-data/metadata.json +6 -0
- package/build/resources/ui-ux-data/products.json +1058 -0
- package/build/resources/ui-ux-data/react-performance.json +574 -0
- package/build/resources/ui-ux-data/stacks/astro.json +266 -0
- package/build/resources/ui-ux-data/stacks/flutter.json +626 -0
- package/build/resources/ui-ux-data/stacks/html-tailwind.json +662 -0
- package/build/resources/ui-ux-data/stacks/jetpack-compose.json +626 -0
- package/build/resources/ui-ux-data/stacks/nextjs.json +218 -0
- package/build/resources/ui-ux-data/stacks/nuxt-ui.json +14 -0
- package/build/resources/ui-ux-data/stacks/nuxtjs.json +182 -0
- package/build/resources/ui-ux-data/stacks/react-native.json +350 -0
- package/build/resources/ui-ux-data/stacks/react.json +530 -0
- package/build/resources/ui-ux-data/stacks/shadcn.json +566 -0
- package/build/resources/ui-ux-data/stacks/svelte.json +134 -0
- package/build/resources/ui-ux-data/stacks/swiftui.json +26 -0
- package/build/resources/ui-ux-data/stacks/vue.json +170 -0
- package/build/resources/ui-ux-data/styles.json +1610 -0
- package/build/resources/ui-ux-data/typography.json +743 -0
- package/build/resources/ui-ux-data/ui-reasoning.json +1431 -0
- package/build/resources/ui-ux-data/ux-guidelines.json +1190 -0
- package/build/resources/ui-ux-data/web-interface.json +389 -0
- package/build/schemas/ui-ux-schemas.js +1 -1
- package/build/tools/start_product.js +8 -1
- package/build/tools/start_ui.js +14 -3
- package/build/tools/ui-ux-tools.js +21 -17
- package/build/utils/ui-data-loader.d.ts +18 -2
- package/build/utils/ui-data-loader.js +74 -12
- package/docs/i18n/en.json +4 -2
- package/docs/i18n/ja.json +4 -2
- package/docs/i18n/ko.json +4 -2
- package/docs/i18n/zh-CN.json +4 -2
- package/docs/pages/getting-started.html +3 -0
- package/package.json +2 -1
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"No": "1",
|
|
4
|
+
"Category": "Accessibility",
|
|
5
|
+
"Issue": "Icon Button Labels",
|
|
6
|
+
"Keywords": "icon button aria-label",
|
|
7
|
+
"Platform": "Web",
|
|
8
|
+
"Description": "Icon-only buttons must have accessible names",
|
|
9
|
+
"Do": "Add aria-label to icon buttons",
|
|
10
|
+
"Don't": "Icon button without label",
|
|
11
|
+
"Code Example Good": "<button aria-label='Close'><XIcon /></button>",
|
|
12
|
+
"Code Example Bad": "<button><XIcon /></button>",
|
|
13
|
+
"Severity": "Critical"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"No": "2",
|
|
17
|
+
"Category": "Accessibility",
|
|
18
|
+
"Issue": "Form Control Labels",
|
|
19
|
+
"Keywords": "form input label aria",
|
|
20
|
+
"Platform": "Web",
|
|
21
|
+
"Description": "All form controls need labels or aria-label",
|
|
22
|
+
"Do": "Use label element or aria-label",
|
|
23
|
+
"Don't": "Input without accessible name",
|
|
24
|
+
"Code Example Good": "<label for='email'>Email</label><input id='email' />",
|
|
25
|
+
"Code Example Bad": "<input placeholder='Email' />",
|
|
26
|
+
"Severity": "Critical"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"No": "3",
|
|
30
|
+
"Category": "Accessibility",
|
|
31
|
+
"Issue": "Keyboard Handlers",
|
|
32
|
+
"Keywords": "keyboard onclick onkeydown",
|
|
33
|
+
"Platform": "Web",
|
|
34
|
+
"Description": "Interactive elements must support keyboard interaction",
|
|
35
|
+
"Do": "Add onKeyDown alongside onClick",
|
|
36
|
+
"Don't": "Click-only interaction",
|
|
37
|
+
"Code Example Good": "<div onClick={fn} onKeyDown={fn} tabIndex={0}>",
|
|
38
|
+
"Code Example Bad": "<div onClick={fn}>",
|
|
39
|
+
"Severity": "High"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"No": "4",
|
|
43
|
+
"Category": "Accessibility",
|
|
44
|
+
"Issue": "Semantic HTML",
|
|
45
|
+
"Keywords": "semantic button a label",
|
|
46
|
+
"Platform": "Web",
|
|
47
|
+
"Description": "Use semantic HTML before ARIA attributes",
|
|
48
|
+
"Do": "Use button/a/label elements",
|
|
49
|
+
"Don't": "Div with role attribute",
|
|
50
|
+
"Code Example Good": "<button onClick={fn}>Submit</button>",
|
|
51
|
+
"Code Example Bad": "<div role='button' onClick={fn}>Submit</div>",
|
|
52
|
+
"Severity": "High"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"No": "5",
|
|
56
|
+
"Category": "Accessibility",
|
|
57
|
+
"Issue": "Aria Live",
|
|
58
|
+
"Keywords": "aria-live polite async",
|
|
59
|
+
"Platform": "Web",
|
|
60
|
+
"Description": "Async updates need aria-live for screen readers",
|
|
61
|
+
"Do": "Add aria-live='polite' for dynamic content",
|
|
62
|
+
"Don't": "Silent async updates",
|
|
63
|
+
"Code Example Good": "<div aria-live='polite'>{status}</div>",
|
|
64
|
+
"Code Example Bad": "<div>{status}</div> // no announcement",
|
|
65
|
+
"Severity": "Medium"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"No": "6",
|
|
69
|
+
"Category": "Accessibility",
|
|
70
|
+
"Issue": "Decorative Icons",
|
|
71
|
+
"Keywords": "aria-hidden decorative icon",
|
|
72
|
+
"Platform": "Web",
|
|
73
|
+
"Description": "Decorative icons should be hidden from screen readers",
|
|
74
|
+
"Do": "Add aria-hidden='true' to decorative icons",
|
|
75
|
+
"Don't": "Decorative icon announced",
|
|
76
|
+
"Code Example Good": "<Icon aria-hidden='true' />",
|
|
77
|
+
"Code Example Bad": "<Icon /> // announced as 'image'",
|
|
78
|
+
"Severity": "Medium"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"No": "7",
|
|
82
|
+
"Category": "Focus",
|
|
83
|
+
"Issue": "Visible Focus States",
|
|
84
|
+
"Keywords": "focus-visible outline ring",
|
|
85
|
+
"Platform": "Web",
|
|
86
|
+
"Description": "All interactive elements need visible focus states",
|
|
87
|
+
"Do": "Use :focus-visible with ring/outline",
|
|
88
|
+
"Don't": "No focus indication",
|
|
89
|
+
"Code Example Good": "focus-visible:ring-2 focus-visible:ring-blue-500",
|
|
90
|
+
"Code Example Bad": "outline-none // no replacement",
|
|
91
|
+
"Severity": "Critical"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"No": "8",
|
|
95
|
+
"Category": "Focus",
|
|
96
|
+
"Issue": "Never Remove Outline",
|
|
97
|
+
"Keywords": "outline-none focus replacement",
|
|
98
|
+
"Platform": "Web",
|
|
99
|
+
"Description": "Never remove outline without providing replacement",
|
|
100
|
+
"Do": "Replace outline with visible alternative",
|
|
101
|
+
"Don't": "Remove outline completely",
|
|
102
|
+
"Code Example Good": "focus:outline-none focus:ring-2",
|
|
103
|
+
"Code Example Bad": "focus:outline-none // nothing else",
|
|
104
|
+
"Severity": "Critical"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"No": "9",
|
|
108
|
+
"Category": "Focus",
|
|
109
|
+
"Issue": "Checkbox Radio Hit Target",
|
|
110
|
+
"Keywords": "checkbox radio label target",
|
|
111
|
+
"Platform": "Web",
|
|
112
|
+
"Description": "Checkbox/radio must share hit target with label",
|
|
113
|
+
"Do": "Wrap input and label together",
|
|
114
|
+
"Don't": "Separate tiny checkbox",
|
|
115
|
+
"Code Example Good": "<label class='flex gap-2'><input type='checkbox' /><span>Option</span></label>",
|
|
116
|
+
"Code Example Bad": "<input type='checkbox' id='x' /><label for='x'>Option</label>",
|
|
117
|
+
"Severity": "Medium"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"No": "10",
|
|
121
|
+
"Category": "Forms",
|
|
122
|
+
"Issue": "Autocomplete Attribute",
|
|
123
|
+
"Keywords": "autocomplete input form",
|
|
124
|
+
"Platform": "Web",
|
|
125
|
+
"Description": "Inputs need autocomplete attribute for autofill",
|
|
126
|
+
"Do": "Add appropriate autocomplete value",
|
|
127
|
+
"Don't": "Missing autocomplete",
|
|
128
|
+
"Code Example Good": "<input autocomplete='email' type='email' />",
|
|
129
|
+
"Code Example Bad": "<input type='email' />",
|
|
130
|
+
"Severity": "High"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"No": "11",
|
|
134
|
+
"Category": "Forms",
|
|
135
|
+
"Issue": "Semantic Input Types",
|
|
136
|
+
"Keywords": "input type email tel url",
|
|
137
|
+
"Platform": "Web",
|
|
138
|
+
"Description": "Use semantic input type attributes",
|
|
139
|
+
"Do": "Use email/tel/url/number types",
|
|
140
|
+
"Don't": "text type for everything",
|
|
141
|
+
"Code Example Good": "<input type='email' />",
|
|
142
|
+
"Code Example Bad": "<input type='text' /> // for email",
|
|
143
|
+
"Severity": "Medium"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"No": "12",
|
|
147
|
+
"Category": "Forms",
|
|
148
|
+
"Issue": "Never Block Paste",
|
|
149
|
+
"Keywords": "paste onpaste password",
|
|
150
|
+
"Platform": "Web",
|
|
151
|
+
"Description": "Never prevent paste functionality",
|
|
152
|
+
"Do": "Allow paste on all inputs",
|
|
153
|
+
"Don't": "Block paste on password/code",
|
|
154
|
+
"Code Example Good": "<input type='password' />",
|
|
155
|
+
"Code Example Bad": "<input onPaste={e => e.preventDefault()} />",
|
|
156
|
+
"Severity": "High"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"No": "13",
|
|
160
|
+
"Category": "Forms",
|
|
161
|
+
"Issue": "Spellcheck Disable",
|
|
162
|
+
"Keywords": "spellcheck email code",
|
|
163
|
+
"Platform": "Web",
|
|
164
|
+
"Description": "Disable spellcheck on emails and codes",
|
|
165
|
+
"Do": "Set spellcheck='false' on codes",
|
|
166
|
+
"Don't": "Spellcheck on technical input",
|
|
167
|
+
"Code Example Good": "<input spellCheck='false' type='email' />",
|
|
168
|
+
"Code Example Bad": "<input type='email' /> // red squiggles",
|
|
169
|
+
"Severity": "Low"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"No": "14",
|
|
173
|
+
"Category": "Forms",
|
|
174
|
+
"Issue": "Submit Button Enabled",
|
|
175
|
+
"Keywords": "submit button disabled loading",
|
|
176
|
+
"Platform": "Web",
|
|
177
|
+
"Description": "Keep submit enabled and show spinner during requests",
|
|
178
|
+
"Do": "Show loading spinner keep enabled",
|
|
179
|
+
"Don't": "Disable button during submit",
|
|
180
|
+
"Code Example Good": "<button>{loading ? <Spinner /> : 'Submit'}</button>",
|
|
181
|
+
"Code Example Bad": "<button disabled={loading}>Submit</button>",
|
|
182
|
+
"Severity": "Medium"
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"No": "15",
|
|
186
|
+
"Category": "Forms",
|
|
187
|
+
"Issue": "Inline Errors",
|
|
188
|
+
"Keywords": "error message inline focus",
|
|
189
|
+
"Platform": "Web",
|
|
190
|
+
"Description": "Show error messages inline near the problem field",
|
|
191
|
+
"Do": "Inline error with focus on first error",
|
|
192
|
+
"Don't": "Single error at top",
|
|
193
|
+
"Code Example Good": "<input /><span class='text-red-500'>{error}</span>",
|
|
194
|
+
"Code Example Bad": "<div class='error'>{allErrors}</div> // at top",
|
|
195
|
+
"Severity": "High"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"No": "16",
|
|
199
|
+
"Category": "Performance",
|
|
200
|
+
"Issue": "Virtualize Lists",
|
|
201
|
+
"Keywords": "virtualize list 50 items",
|
|
202
|
+
"Platform": "Web",
|
|
203
|
+
"Description": "Virtualize lists exceeding 50 items",
|
|
204
|
+
"Do": "Use virtual list for large datasets",
|
|
205
|
+
"Don't": "Render all items",
|
|
206
|
+
"Code Example Good": "<VirtualList items={items} />",
|
|
207
|
+
"Code Example Bad": "items.map(item => <Item />)",
|
|
208
|
+
"Severity": "High"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"No": "17",
|
|
212
|
+
"Category": "Performance",
|
|
213
|
+
"Issue": "Avoid Layout Reads",
|
|
214
|
+
"Keywords": "layout read render getboundingclientrect",
|
|
215
|
+
"Platform": "Web",
|
|
216
|
+
"Description": "Avoid layout reads during render phase",
|
|
217
|
+
"Do": "Read layout in effects or callbacks",
|
|
218
|
+
"Don't": "getBoundingClientRect in render",
|
|
219
|
+
"Code Example Good": "useEffect(() => { el.getBoundingClientRect() })",
|
|
220
|
+
"Code Example Bad": "const rect = el.getBoundingClientRect() // in render",
|
|
221
|
+
"Severity": "Medium"
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"No": "18",
|
|
225
|
+
"Category": "Performance",
|
|
226
|
+
"Issue": "Batch DOM Operations",
|
|
227
|
+
"Keywords": "batch dom write read",
|
|
228
|
+
"Platform": "Web",
|
|
229
|
+
"Description": "Group DOM operations to minimize reflows",
|
|
230
|
+
"Do": "Batch writes then reads",
|
|
231
|
+
"Don't": "Interleave reads and writes",
|
|
232
|
+
"Code Example Good": "writes.forEach(w => w()); reads.forEach(r => r())",
|
|
233
|
+
"Code Example Bad": "write(); read(); write(); read(); // thrashing",
|
|
234
|
+
"Severity": "Medium"
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"No": "19",
|
|
238
|
+
"Category": "Performance",
|
|
239
|
+
"Issue": "Preconnect CDN",
|
|
240
|
+
"Keywords": "preconnect link cdn",
|
|
241
|
+
"Platform": "Web",
|
|
242
|
+
"Description": "Add preconnect links for CDN domains",
|
|
243
|
+
"Do": "Preconnect to known domains",
|
|
244
|
+
"Don't": "<link rel='preconnect' href='https://cdn.example.com' />",
|
|
245
|
+
"Code Example Good": "// no preconnect hint",
|
|
246
|
+
"Code Example Bad": "Low"
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"No": "20",
|
|
250
|
+
"Category": "Performance",
|
|
251
|
+
"Issue": "Lazy Load Images",
|
|
252
|
+
"Keywords": "lazy loading image below-fold",
|
|
253
|
+
"Platform": "Web",
|
|
254
|
+
"Description": "Lazy-load images below the fold",
|
|
255
|
+
"Do": "Use loading='lazy' for below-fold images",
|
|
256
|
+
"Don't": "Load all images eagerly",
|
|
257
|
+
"Code Example Good": "<img loading='lazy' src='...' />",
|
|
258
|
+
"Code Example Bad": "<img src='...' /> // above fold only",
|
|
259
|
+
"Severity": "Medium"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"No": "21",
|
|
263
|
+
"Category": "State",
|
|
264
|
+
"Issue": "URL Reflects State",
|
|
265
|
+
"Keywords": "url state query params",
|
|
266
|
+
"Platform": "Web",
|
|
267
|
+
"Description": "URL should reflect current UI state",
|
|
268
|
+
"Do": "Sync filters/tabs/pagination to URL",
|
|
269
|
+
"Don't": "State only in memory",
|
|
270
|
+
"Code Example Good": "?tab=settings&page=2",
|
|
271
|
+
"Code Example Bad": "useState only // lost on refresh",
|
|
272
|
+
"Severity": "High"
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"No": "22",
|
|
276
|
+
"Category": "State",
|
|
277
|
+
"Issue": "Deep Linking",
|
|
278
|
+
"Keywords": "deep link stateful component",
|
|
279
|
+
"Platform": "Web",
|
|
280
|
+
"Description": "Stateful components should support deep-linking",
|
|
281
|
+
"Do": "Enable sharing current view via URL",
|
|
282
|
+
"Don't": "No shareable state",
|
|
283
|
+
"Code Example Good": "router.push({ query: { ...filters } })",
|
|
284
|
+
"Code Example Bad": "setFilters(f) // not in URL",
|
|
285
|
+
"Severity": "Medium"
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
"No": "23",
|
|
289
|
+
"Category": "State",
|
|
290
|
+
"Issue": "Confirm Destructive Actions",
|
|
291
|
+
"Keywords": "confirm destructive delete modal",
|
|
292
|
+
"Platform": "Web",
|
|
293
|
+
"Description": "Destructive actions require confirmation",
|
|
294
|
+
"Do": "Show confirmation dialog before delete",
|
|
295
|
+
"Don't": "Delete without confirmation",
|
|
296
|
+
"Code Example Good": "if (confirm('Delete?')) delete()",
|
|
297
|
+
"Code Example Bad": "onClick={delete} // no confirmation",
|
|
298
|
+
"Severity": "High"
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
"No": "24",
|
|
302
|
+
"Category": "Typography",
|
|
303
|
+
"Issue": "Proper Unicode",
|
|
304
|
+
"Keywords": "unicode ellipsis quotes",
|
|
305
|
+
"Platform": "Web",
|
|
306
|
+
"Description": "Use proper Unicode characters",
|
|
307
|
+
"Do": "Use ... curly quotes proper dashes",
|
|
308
|
+
"Don't": "ASCII approximations",
|
|
309
|
+
"Code Example Good": "'Hello...' with proper ellipsis",
|
|
310
|
+
"Code Example Bad": "'Hello...' with three dots",
|
|
311
|
+
"Severity": "Low"
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
"No": "25",
|
|
315
|
+
"Category": "Typography",
|
|
316
|
+
"Issue": "Text Overflow",
|
|
317
|
+
"Keywords": "truncate line-clamp overflow",
|
|
318
|
+
"Platform": "Web",
|
|
319
|
+
"Description": "Handle text overflow properly",
|
|
320
|
+
"Do": "Use truncate/line-clamp/break-words",
|
|
321
|
+
"Don't": "Text overflows container",
|
|
322
|
+
"Code Example Good": "<p class='truncate'>Long text...</p>",
|
|
323
|
+
"Code Example Bad": "<p>Long text...</p> // overflows",
|
|
324
|
+
"Severity": "Medium"
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
"No": "26",
|
|
328
|
+
"Category": "Typography",
|
|
329
|
+
"Issue": "Non-Breaking Spaces",
|
|
330
|
+
"Keywords": "nbsp unit brand",
|
|
331
|
+
"Platform": "Web",
|
|
332
|
+
"Description": "Use non-breaking spaces for units and brand names",
|
|
333
|
+
"Do": "Use between number and unit",
|
|
334
|
+
"Don't": "10 kg or Next.js 14",
|
|
335
|
+
"Code Example Good": "10 kg // may wrap",
|
|
336
|
+
"Code Example Bad": "Low"
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"No": "27",
|
|
340
|
+
"Category": "Anti-Pattern",
|
|
341
|
+
"Issue": "No Zoom Disable",
|
|
342
|
+
"Keywords": "viewport zoom disable",
|
|
343
|
+
"Platform": "Web",
|
|
344
|
+
"Description": "Never disable zoom in viewport meta",
|
|
345
|
+
"Do": "Allow user zoom",
|
|
346
|
+
"Don't": "<meta name='viewport' content='width=device-width'>",
|
|
347
|
+
"Code Example Good": "<meta name='viewport' content='maximum-scale=1'>",
|
|
348
|
+
"Code Example Bad": "Critical"
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
"No": "28",
|
|
352
|
+
"Category": "Anti-Pattern",
|
|
353
|
+
"Issue": "No Transition All",
|
|
354
|
+
"Keywords": "transition all specific",
|
|
355
|
+
"Platform": "Web",
|
|
356
|
+
"Description": "Avoid transition: all - specify properties",
|
|
357
|
+
"Do": "Transition specific properties",
|
|
358
|
+
"Don't": "transition: all",
|
|
359
|
+
"Code Example Good": "transition-colors duration-200",
|
|
360
|
+
"Code Example Bad": "transition-all duration-200",
|
|
361
|
+
"Severity": "Medium"
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
"No": "29",
|
|
365
|
+
"Category": "Anti-Pattern",
|
|
366
|
+
"Issue": "Outline Replacement",
|
|
367
|
+
"Keywords": "outline-none ring focus",
|
|
368
|
+
"Platform": "Web",
|
|
369
|
+
"Description": "Never use outline-none without replacement",
|
|
370
|
+
"Do": "Provide visible focus replacement",
|
|
371
|
+
"Don't": "Remove outline with nothing",
|
|
372
|
+
"Code Example Good": "focus:outline-none focus:ring-2 focus:ring-blue-500",
|
|
373
|
+
"Code Example Bad": "focus:outline-none // alone",
|
|
374
|
+
"Severity": "Critical"
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
"No": "30",
|
|
378
|
+
"Category": "Anti-Pattern",
|
|
379
|
+
"Issue": "No Hardcoded Dates",
|
|
380
|
+
"Keywords": "date format intl locale",
|
|
381
|
+
"Platform": "Web",
|
|
382
|
+
"Description": "Use Intl for date/number formatting",
|
|
383
|
+
"Do": "Use Intl.DateTimeFormat",
|
|
384
|
+
"Don't": "Hardcoded date format",
|
|
385
|
+
"Code Example Good": "new Intl.DateTimeFormat('en').format(date)",
|
|
386
|
+
"Code Example Bad": "date.toLocaleDateString() // or manual format",
|
|
387
|
+
"Severity": "Medium"
|
|
388
|
+
}
|
|
389
|
+
]
|
|
@@ -69,7 +69,7 @@ export const uiSearchSchema = {
|
|
|
69
69
|
};
|
|
70
70
|
export const syncUiDataSchema = {
|
|
71
71
|
name: "sync_ui_data",
|
|
72
|
-
description: "同步 UI/UX 数据到本地缓存。从 npm 包 uipro-cli 下载最新数据,支持自动检查更新和强制同步。数据存储在 ~/.mcp-probe-kit/ui-ux-data
|
|
72
|
+
description: "同步 UI/UX 数据到本地缓存。从 npm 包 uipro-cli 下载最新数据,支持自动检查更新和强制同步。数据存储在 ~/.mcp-probe-kit/ui-ux-data/,默认在下次启动时生效以保证当前会话一致性。",
|
|
73
73
|
inputSchema: {
|
|
74
74
|
type: "object",
|
|
75
75
|
properties: {
|
|
@@ -2,6 +2,7 @@ import { parseArgs, getString, getBoolean } from "../utils/parseArgs.js";
|
|
|
2
2
|
import { promises as fs } from "fs";
|
|
3
3
|
import { okStructured } from "../lib/response.js";
|
|
4
4
|
import { renderOrchestrationHeader } from "../lib/orchestration-guidance.js";
|
|
5
|
+
import { buildSkillBridgePlanStep, buildSkillHeaderNote, detectSkillBridge, renderSkillBridgeSection, } from "../lib/skill-bridge.js";
|
|
5
6
|
import { WorkflowReportSchema } from "../schemas/structured-output.js";
|
|
6
7
|
import { reportToolProgress, throwIfAborted, } from "../lib/tool-execution-context.js";
|
|
7
8
|
/**
|
|
@@ -76,6 +77,9 @@ export async function startProduct(args, context) {
|
|
|
76
77
|
isError: true,
|
|
77
78
|
};
|
|
78
79
|
}
|
|
80
|
+
const skillBridge = detectSkillBridge('start_product');
|
|
81
|
+
const skillBridgeStep = buildSkillBridgePlanStep(skillBridge);
|
|
82
|
+
const skillBridgeSection = renderSkillBridgeSection(skillBridge);
|
|
79
83
|
const header = renderOrchestrationHeader({
|
|
80
84
|
tool: 'start_product',
|
|
81
85
|
goal: `完成产品设计工作流:${productName}`,
|
|
@@ -83,8 +87,9 @@ export async function startProduct(args, context) {
|
|
|
83
87
|
'按 delegated plan 顺序调用工具',
|
|
84
88
|
'生成 PRD、原型、设计系统与 HTML 原型',
|
|
85
89
|
],
|
|
90
|
+
notes: [buildSkillHeaderNote(skillBridge)],
|
|
86
91
|
});
|
|
87
|
-
const guidanceText = header + `# 🚀 产品设计工作流执行指导
|
|
92
|
+
const guidanceText = header + skillBridgeSection + `# 🚀 产品设计工作流执行指导
|
|
88
93
|
|
|
89
94
|
基于${requirementsSource},请按照以下步骤完成从需求到 HTML 原型的完整产品设计流程。
|
|
90
95
|
|
|
@@ -308,6 +313,7 @@ ${!skipDesignSystem ? `├── design-system.json # 设计系统配
|
|
|
308
313
|
const plan = {
|
|
309
314
|
mode: 'delegated',
|
|
310
315
|
steps: [
|
|
316
|
+
skillBridgeStep,
|
|
311
317
|
{
|
|
312
318
|
id: 'context',
|
|
313
319
|
tool: 'init_project_context',
|
|
@@ -473,6 +479,7 @@ ${!skipDesignSystem ? `├── design-system.json # 设计系统配
|
|
|
473
479
|
],
|
|
474
480
|
metadata: {
|
|
475
481
|
plan,
|
|
482
|
+
skills: skillBridge,
|
|
476
483
|
},
|
|
477
484
|
};
|
|
478
485
|
await reportToolProgress(context, 95, "start_product: 工作流输出已生成");
|
package/build/tools/start_ui.js
CHANGED
|
@@ -11,6 +11,7 @@ import { parseArgs, getString, getNumber } from "../utils/parseArgs.js";
|
|
|
11
11
|
import { getReasoningEngine } from "./ui-ux-tools.js";
|
|
12
12
|
import { okStructured } from "../lib/response.js";
|
|
13
13
|
import { renderOrchestrationHeader } from "../lib/orchestration-guidance.js";
|
|
14
|
+
import { buildSkillBridgePlanStep, buildSkillHeaderNote, detectSkillBridge, renderSkillBridgeSection, } from "../lib/skill-bridge.js";
|
|
14
15
|
import { UIReportSchema, RequirementsLoopSchema } from "../schemas/structured-output.js";
|
|
15
16
|
import { detectProjectType } from "../lib/project-detector.js";
|
|
16
17
|
import { reportToolProgress, throwIfAborted, } from "../lib/tool-execution-context.js";
|
|
@@ -519,6 +520,10 @@ export async function startUi(args, context) {
|
|
|
519
520
|
if (profileDecision.warning) {
|
|
520
521
|
headerNotes.push(profileDecision.warning);
|
|
521
522
|
}
|
|
523
|
+
const skillBridge = detectSkillBridge('start_ui');
|
|
524
|
+
const skillBridgeStep = buildSkillBridgePlanStep(skillBridge);
|
|
525
|
+
const skillBridgeSection = renderSkillBridgeSection(skillBridge);
|
|
526
|
+
headerNotes.push(buildSkillHeaderNote(skillBridge));
|
|
522
527
|
// 验证 mode 参数
|
|
523
528
|
const validModes = ["auto", "manual"];
|
|
524
529
|
if (mode && !validModes.includes(mode)) {
|
|
@@ -583,6 +588,7 @@ start_ui <描述> --requirements_mode=loop
|
|
|
583
588
|
const plan = {
|
|
584
589
|
mode: 'delegated',
|
|
585
590
|
steps: [
|
|
591
|
+
skillBridgeStep,
|
|
586
592
|
{
|
|
587
593
|
id: 'loop-1',
|
|
588
594
|
tool: 'ask_user',
|
|
@@ -666,7 +672,7 @@ start_ui <描述> --requirements_mode=loop
|
|
|
666
672
|
const loopTemplate = profileDecision.resolved === 'strict'
|
|
667
673
|
? LOOP_PROMPT_TEMPLATE_STRICT
|
|
668
674
|
: LOOP_PROMPT_TEMPLATE_GUIDED;
|
|
669
|
-
const guide = header + loopTemplate
|
|
675
|
+
const guide = header + skillBridgeSection + loopTemplate
|
|
670
676
|
.replace(/{description}/g, description)
|
|
671
677
|
.replace(/{question_budget}/g, String(questionBudget))
|
|
672
678
|
.replace(/{assumption_cap}/g, String(assumptionCap));
|
|
@@ -696,6 +702,7 @@ start_ui <描述> --requirements_mode=loop
|
|
|
696
702
|
metadata: {
|
|
697
703
|
plan,
|
|
698
704
|
template: templateMeta,
|
|
705
|
+
skills: skillBridge,
|
|
699
706
|
},
|
|
700
707
|
};
|
|
701
708
|
await reportToolProgress(context, 95, "start_ui: loop 输出已生成");
|
|
@@ -808,6 +815,7 @@ ${recommendation.reasoning}
|
|
|
808
815
|
const plan = {
|
|
809
816
|
mode: 'delegated',
|
|
810
817
|
steps: [
|
|
818
|
+
skillBridgeStep,
|
|
811
819
|
{
|
|
812
820
|
id: 'context',
|
|
813
821
|
tool: 'init_project_context',
|
|
@@ -872,7 +880,7 @@ ${recommendation.reasoning}
|
|
|
872
880
|
],
|
|
873
881
|
notes: headerNotes,
|
|
874
882
|
});
|
|
875
|
-
const smartPlan = header + (profileDecision.resolved === 'strict' ? smartPlanStrict : smartPlanGuided);
|
|
883
|
+
const smartPlan = header + skillBridgeSection + (profileDecision.resolved === 'strict' ? smartPlanStrict : smartPlanGuided);
|
|
876
884
|
// Create structured UI report for auto mode
|
|
877
885
|
const uiReport = {
|
|
878
886
|
summary: `智能 UI 开发:${description}`,
|
|
@@ -941,6 +949,7 @@ ${recommendation.reasoning}
|
|
|
941
949
|
metadata: {
|
|
942
950
|
plan,
|
|
943
951
|
template: templateMeta,
|
|
952
|
+
skills: skillBridge,
|
|
944
953
|
},
|
|
945
954
|
};
|
|
946
955
|
await reportToolProgress(context, 95, "start_ui: auto 输出已生成");
|
|
@@ -1002,7 +1011,7 @@ start_ui "设置页面" --framework=react
|
|
|
1002
1011
|
const baseTemplate = profileDecision.resolved === 'strict'
|
|
1003
1012
|
? PROMPT_TEMPLATE_STRICT
|
|
1004
1013
|
: PROMPT_TEMPLATE_GUIDED;
|
|
1005
|
-
let guide = header + baseTemplate;
|
|
1014
|
+
let guide = header + skillBridgeSection + baseTemplate;
|
|
1006
1015
|
guide = safeReplace(guide, '{description}', escapeJson(description));
|
|
1007
1016
|
guide = safeReplace(guide, '{productType}', productType);
|
|
1008
1017
|
guide = safeReplace(guide, '{framework}', framework);
|
|
@@ -1010,6 +1019,7 @@ start_ui "设置页面" --framework=react
|
|
|
1010
1019
|
const plan = {
|
|
1011
1020
|
mode: 'delegated',
|
|
1012
1021
|
steps: [
|
|
1022
|
+
skillBridgeStep,
|
|
1013
1023
|
{
|
|
1014
1024
|
id: 'context',
|
|
1015
1025
|
tool: 'init_project_context',
|
|
@@ -1132,6 +1142,7 @@ start_ui "设置页面" --framework=react
|
|
|
1132
1142
|
metadata: {
|
|
1133
1143
|
plan,
|
|
1134
1144
|
template: templateMeta,
|
|
1145
|
+
skills: skillBridge,
|
|
1135
1146
|
},
|
|
1136
1147
|
};
|
|
1137
1148
|
await reportToolProgress(context, 95, "start_ui: manual 输出已生成");
|
|
@@ -774,7 +774,7 @@ export async function syncUiData(args, context) {
|
|
|
774
774
|
console.log('Failed to check update, proceeding with sync...');
|
|
775
775
|
}
|
|
776
776
|
}
|
|
777
|
-
//
|
|
777
|
+
// 执行同步(下载并写入缓存,当前会话不热切换)
|
|
778
778
|
throwIfAborted(context?.signal, 'sync_ui_data 已取消');
|
|
779
779
|
await reportToolProgress(context, 30, 'sync_ui_data: 下载并处理数据');
|
|
780
780
|
await syncUIDataToCache(force, verbose, {
|
|
@@ -783,16 +783,14 @@ export async function syncUiData(args, context) {
|
|
|
783
783
|
await reportToolProgress(context, 30 + Math.round(progress * 0.6), `sync_ui_data: ${message}`);
|
|
784
784
|
},
|
|
785
785
|
});
|
|
786
|
-
// 重新加载数据
|
|
787
786
|
throwIfAborted(context?.signal, 'sync_ui_data 已取消');
|
|
788
|
-
await reportToolProgress(context, 92, 'sync_ui_data:
|
|
789
|
-
if (dataLoader) {
|
|
790
|
-
await dataLoader.reload();
|
|
791
|
-
}
|
|
792
|
-
const cacheDir = dataLoader?.getCacheManager().getCacheDir() || '';
|
|
787
|
+
await reportToolProgress(context, 92, 'sync_ui_data: 记录会话状态');
|
|
793
788
|
// 获取同步的数据统计
|
|
794
789
|
const loader = await getDataLoader();
|
|
790
|
+
const cacheDir = loader.getCacheManager().getCacheDir() || '';
|
|
791
|
+
const metadata = loader.getCacheManager().getMetadata();
|
|
795
792
|
const searchEngine = loader.getSearchEngine();
|
|
793
|
+
const sessionInfo = loader.getSessionInfo();
|
|
796
794
|
const syncedData = {
|
|
797
795
|
summary: "UI/UX 数据同步成功",
|
|
798
796
|
status: 'success',
|
|
@@ -802,20 +800,26 @@ export async function syncUiData(args, context) {
|
|
|
802
800
|
components: (searchEngine.getCategoryData('products') || []).length,
|
|
803
801
|
patterns: (searchEngine.getCategoryData('landing') || []).length,
|
|
804
802
|
},
|
|
803
|
+
version: metadata?.version,
|
|
805
804
|
timestamp: new Date().toISOString(),
|
|
806
805
|
};
|
|
807
806
|
await reportToolProgress(context, 100, 'sync_ui_data: 同步完成');
|
|
808
807
|
return okStructured(`✅ UI/UX 数据同步成功
|
|
809
|
-
|
|
810
|
-
数据已更新到缓存目录: ${cacheDir}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
-
|
|
815
|
-
-
|
|
816
|
-
-
|
|
817
|
-
|
|
818
|
-
|
|
808
|
+
|
|
809
|
+
数据已更新到缓存目录: ${cacheDir}
|
|
810
|
+
下载版本: ${metadata?.version || 'unknown'}
|
|
811
|
+
|
|
812
|
+
**同步统计:**
|
|
813
|
+
- 颜色: ${syncedData.synced.colors} 条
|
|
814
|
+
- 图标: ${syncedData.synced.icons} 条
|
|
815
|
+
- 组件: ${syncedData.synced.components} 条
|
|
816
|
+
- 模式: ${syncedData.synced.patterns} 条
|
|
817
|
+
|
|
818
|
+
**会话状态:**
|
|
819
|
+
- 当前会话使用版本: ${sessionInfo.activeVersion || 'unknown'}(source: ${sessionInfo.source})
|
|
820
|
+
- 下次启动生效版本: ${sessionInfo.pendingVersion || metadata?.version || 'unknown'}
|
|
821
|
+
|
|
822
|
+
**提示:** 为保证会话内结果一致,新数据将在下次启动后生效。
|
|
819
823
|
`, syncedData, {
|
|
820
824
|
schema: (await import('../schemas/output/ui-ux-tools.js')).SyncReportSchema,
|
|
821
825
|
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 三层数据策略:
|
|
5
5
|
* 1. 内嵌数据(构建时同步)
|
|
6
6
|
* 2. 缓存数据(运行时更新)
|
|
7
|
-
* 3.
|
|
7
|
+
* 3. 自动后台同步(当前会话不热切换,下次启动生效)
|
|
8
8
|
*/
|
|
9
9
|
import { CacheManager } from './cache-manager.js';
|
|
10
10
|
import { UISearchEngine } from './ui-search-engine.js';
|
|
@@ -12,6 +12,13 @@ export interface DataLoaderOptions {
|
|
|
12
12
|
useCache?: boolean;
|
|
13
13
|
autoUpdate?: boolean;
|
|
14
14
|
}
|
|
15
|
+
export interface UIDataSessionInfo {
|
|
16
|
+
source: 'cache' | 'embedded';
|
|
17
|
+
activeVersion?: string;
|
|
18
|
+
pendingVersion?: string;
|
|
19
|
+
lastCheckedAt?: string;
|
|
20
|
+
hasPendingUpdate: boolean;
|
|
21
|
+
}
|
|
15
22
|
/**
|
|
16
23
|
* UI/UX 数据加载器
|
|
17
24
|
*/
|
|
@@ -19,7 +26,11 @@ export declare class UIDataLoader {
|
|
|
19
26
|
private cacheManager;
|
|
20
27
|
private searchEngine;
|
|
21
28
|
private useCache;
|
|
29
|
+
private autoUpdate;
|
|
22
30
|
private loaded;
|
|
31
|
+
private autoSyncRunning;
|
|
32
|
+
private autoSyncFailureUntil;
|
|
33
|
+
private sessionInfo;
|
|
23
34
|
constructor(options?: DataLoaderOptions);
|
|
24
35
|
/**
|
|
25
36
|
* 加载数据
|
|
@@ -33,6 +44,7 @@ export declare class UIDataLoader {
|
|
|
33
44
|
* 从内嵌数据加载
|
|
34
45
|
*/
|
|
35
46
|
private loadFromEmbedded;
|
|
47
|
+
private readEmbeddedVersion;
|
|
36
48
|
/**
|
|
37
49
|
* 提取类别名称
|
|
38
50
|
*/
|
|
@@ -50,7 +62,11 @@ export declare class UIDataLoader {
|
|
|
50
62
|
*/
|
|
51
63
|
getCacheManager(): CacheManager;
|
|
52
64
|
/**
|
|
53
|
-
*
|
|
65
|
+
* 获取当前会话的数据状态
|
|
66
|
+
*/
|
|
67
|
+
getSessionInfo(): UIDataSessionInfo;
|
|
68
|
+
/**
|
|
69
|
+
* 重新加载数据(手动触发时使用)
|
|
54
70
|
*/
|
|
55
71
|
reload(): Promise<void>;
|
|
56
72
|
}
|