@codemcp/agentskills-core 1.3.1 → 1.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.
@@ -1,660 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { validateSkill } from "../validator.js";
3
- describe("SkillValidator", () => {
4
- describe("Valid Skills", () => {
5
- it("should validate skills with required and optional fields", () => {
6
- const basicSkill = {
7
- metadata: { name: "test-skill", description: "A test skill" },
8
- body: "# Test"
9
- };
10
- expect(validateSkill(basicSkill).valid).toBe(true);
11
- const fullSkill = {
12
- metadata: {
13
- name: "full-skill",
14
- description: "Full skill",
15
- license: "MIT",
16
- compatibility: "claude-3.5-sonnet",
17
- metadata: { author: "Test", version: "1.0.0", tags: ["test"] },
18
- allowedTools: ["bash"]
19
- },
20
- body: "# Full"
21
- };
22
- expect(validateSkill(fullSkill).valid).toBe(true);
23
- });
24
- it("should validate boundary lengths", () => {
25
- expect(validateSkill({
26
- metadata: { name: "a".repeat(64), description: "test" },
27
- body: ""
28
- }).valid).toBe(true);
29
- expect(validateSkill({
30
- metadata: { name: "test", description: "a".repeat(1024) },
31
- body: ""
32
- }).valid).toBe(true);
33
- expect(validateSkill({
34
- metadata: {
35
- name: "test",
36
- description: "test",
37
- compatibility: "a".repeat(500)
38
- },
39
- body: ""
40
- }).valid).toBe(true);
41
- });
42
- });
43
- describe("Name Validation", () => {
44
- it.each([
45
- [
46
- "missing",
47
- { name: undefined, description: "test" },
48
- "MISSING_FIELD"
49
- ],
50
- ["empty", { name: "", description: "test" }, "INVALID_NAME_LENGTH"],
51
- [
52
- "null",
53
- { name: null, description: "test" },
54
- "MISSING_FIELD"
55
- ],
56
- [
57
- "too long",
58
- { name: "a".repeat(65), description: "test" },
59
- "INVALID_NAME_LENGTH"
60
- ]
61
- ])("should fail for %s name", (_description, metadata, code) => {
62
- const result = validateSkill({
63
- metadata: metadata,
64
- body: ""
65
- });
66
- expect(result.valid).toBe(false);
67
- expect(result.errors.some((e) => e.code === code)).toBe(true);
68
- });
69
- it.each([
70
- ["Test-Skill", "uppercase"],
71
- ["test skill", "spaces"],
72
- ["test_skill", "underscores"],
73
- ["test.skill", "dots"],
74
- ["test@skill", "special chars"],
75
- ["test-skill-日本語", "unicode"],
76
- ["test-skill-🚀", "emojis"],
77
- ["-test", "leading hyphen"],
78
- ["test-", "trailing hyphen"],
79
- ["test--skill", "consecutive hyphens"]
80
- ])("should fail for name with %s", (name) => {
81
- const result = validateSkill({
82
- metadata: { name, description: "test" },
83
- body: ""
84
- });
85
- expect(result.valid).toBe(false);
86
- expect(result.errors.some((e) => e.code === "INVALID_NAME_FORMAT")).toBe(true);
87
- });
88
- });
89
- describe("Description Validation", () => {
90
- it.each([
91
- [
92
- "missing",
93
- { name: "test", description: undefined },
94
- "MISSING_FIELD"
95
- ],
96
- [
97
- "empty",
98
- { name: "test", description: "" },
99
- "INVALID_DESCRIPTION_LENGTH"
100
- ],
101
- [
102
- "null",
103
- { name: "test", description: null },
104
- "MISSING_FIELD"
105
- ],
106
- [
107
- "too long",
108
- { name: "test", description: "a".repeat(1025) },
109
- "INVALID_DESCRIPTION_LENGTH"
110
- ]
111
- ])("should fail for %s description", (_description, metadata, code) => {
112
- const result = validateSkill({
113
- metadata: metadata,
114
- body: ""
115
- });
116
- expect(result.valid).toBe(false);
117
- expect(result.errors.some((e) => e.code === code)).toBe(true);
118
- });
119
- it("should allow special characters and newlines", () => {
120
- expect(validateSkill({
121
- metadata: {
122
- name: "test",
123
- description: 'émojis 🚀, quotes "nested", <>&'
124
- },
125
- body: ""
126
- }).valid).toBe(true);
127
- expect(validateSkill({
128
- metadata: { name: "test", description: "Line 1\nLine 2" },
129
- body: ""
130
- }).valid).toBe(true);
131
- });
132
- });
133
- describe("Optional Field Validation", () => {
134
- it("should validate compatibility length", () => {
135
- expect(validateSkill({
136
- metadata: {
137
- name: "test",
138
- description: "test",
139
- compatibility: "a".repeat(501)
140
- },
141
- body: ""
142
- }).valid).toBe(false);
143
- expect(validateSkill({
144
- metadata: { name: "test", description: "test", compatibility: "" },
145
- body: ""
146
- }).valid).toBe(true);
147
- });
148
- it.each([
149
- [
150
- "metadata as string",
151
- { metadata: "string" },
152
- "INVALID_FIELD_TYPE"
153
- ],
154
- [
155
- "metadata as array",
156
- { metadata: ["array"] },
157
- "INVALID_FIELD_TYPE"
158
- ],
159
- ["metadata as null", { metadata: null }, "INVALID_FIELD_TYPE"],
160
- [
161
- "allowedTools as string",
162
- { allowedTools: "string" },
163
- "INVALID_FIELD_TYPE"
164
- ],
165
- [
166
- "allowedTools as object",
167
- { allowedTools: {} },
168
- "INVALID_FIELD_TYPE"
169
- ],
170
- [
171
- "allowedTools as null",
172
- { allowedTools: null },
173
- "INVALID_FIELD_TYPE"
174
- ]
175
- ])("should fail for %s", (_description, extraFields, code) => {
176
- const result = validateSkill({
177
- metadata: {
178
- name: "test",
179
- description: "test",
180
- ...extraFields
181
- },
182
- body: ""
183
- });
184
- expect(result.valid).toBe(false);
185
- expect(result.errors.some((e) => e.code === code)).toBe(true);
186
- });
187
- it("should allow valid optional fields", () => {
188
- expect(validateSkill({
189
- metadata: {
190
- name: "test",
191
- description: "test",
192
- metadata: { author: "Test" }
193
- },
194
- body: ""
195
- }).valid).toBe(true);
196
- expect(validateSkill({
197
- metadata: {
198
- name: "test",
199
- description: "test",
200
- allowedTools: ["bash"]
201
- },
202
- body: ""
203
- }).valid).toBe(true);
204
- expect(validateSkill({
205
- metadata: { name: "test", description: "test", license: "MIT" },
206
- body: ""
207
- }).valid).toBe(true);
208
- });
209
- });
210
- describe("Multiple Errors and Warnings", () => {
211
- it("should accumulate all validation errors", () => {
212
- const skill = {
213
- metadata: {
214
- name: "Test-INVALID",
215
- description: "",
216
- compatibility: "a".repeat(600),
217
- metadata: "invalid",
218
- allowedTools: "invalid"
219
- },
220
- body: ""
221
- };
222
- const result = validateSkill(skill);
223
- expect(result.valid).toBe(false);
224
- expect(result.errors.length).toBeGreaterThanOrEqual(5);
225
- });
226
- it("should generate warnings for recommended fields and content length", () => {
227
- const result = validateSkill({
228
- metadata: { name: "test", description: "Short" },
229
- body: "word ".repeat(5000)
230
- });
231
- expect(result.valid).toBe(true);
232
- expect(result.warnings.some((w) => w.code === "MISSING_RECOMMENDED_FIELD")).toBe(true);
233
- expect(result.warnings.some((w) => w.code === "SHORT_DESCRIPTION")).toBe(true);
234
- expect(result.warnings.some((w) => w.code === "LONG_CONTENT")).toBe(true);
235
- });
236
- });
237
- describe("Edge Cases", () => {
238
- it("should handle whitespace and trimming", () => {
239
- expect(validateSkill({
240
- metadata: { name: " ", description: "test" },
241
- body: ""
242
- }).valid).toBe(false);
243
- expect(validateSkill({
244
- metadata: { name: "test", description: " " },
245
- body: ""
246
- }).valid).toBe(false);
247
- expect(validateSkill({
248
- metadata: { name: " test ", description: " desc " },
249
- body: ""
250
- }).valid).toBe(true);
251
- });
252
- it("should handle undefined/empty body", () => {
253
- expect(validateSkill({
254
- metadata: { name: "test", description: "test" },
255
- body: undefined
256
- }).valid).toBe(true);
257
- expect(validateSkill({
258
- metadata: { name: "test", description: "test" },
259
- body: ""
260
- }).valid).toBe(true);
261
- });
262
- it("should handle deeply nested metadata", () => {
263
- const skill = {
264
- metadata: {
265
- name: "test",
266
- description: "test",
267
- metadata: { level1: { level2: { level3: "deep" } } }
268
- },
269
- body: ""
270
- };
271
- expect(validateSkill(skill).valid).toBe(true);
272
- });
273
- });
274
- describe("ValidationResult Structure", () => {
275
- it("should return properly structured result", () => {
276
- const result = validateSkill({
277
- metadata: { name: "INVALID", description: "" },
278
- body: ""
279
- });
280
- expect(result).toHaveProperty("valid");
281
- expect(result).toHaveProperty("errors");
282
- expect(result).toHaveProperty("warnings");
283
- expect(Array.isArray(result.errors)).toBe(true);
284
- expect(Array.isArray(result.warnings)).toBe(true);
285
- result.errors.forEach((e) => {
286
- expect(e).toHaveProperty("code");
287
- expect(e).toHaveProperty("message");
288
- expect(typeof e.code).toBe("string");
289
- expect(typeof e.message).toBe("string");
290
- });
291
- });
292
- });
293
- describe("requiresMcpServers Validation", () => {
294
- it("should pass validation when requires-mcp-servers is not present", () => {
295
- const result = validateSkill({
296
- metadata: {
297
- name: "test-skill",
298
- description: "A test skill without MCP server dependencies"
299
- },
300
- body: "# Skill content"
301
- });
302
- expect(result.valid).toBe(true);
303
- expect(result.errors.length).toBe(0);
304
- });
305
- it("should validate empty requiresMcpServers array", () => {
306
- const result = validateSkill({
307
- metadata: {
308
- name: "test",
309
- description: "test",
310
- requiresMcpServers: []
311
- },
312
- body: ""
313
- });
314
- expect(result.valid).toBe(true);
315
- });
316
- it("should fail when server is missing required field: name", () => {
317
- const result = validateSkill({
318
- metadata: {
319
- name: "test",
320
- description: "test",
321
- requiresMcpServers: [
322
- {
323
- command: "npx",
324
- description: "A test server"
325
- }
326
- ]
327
- },
328
- body: ""
329
- });
330
- expect(result.valid).toBe(false);
331
- expect(result.errors.some((e) => e.code === "MISSING_FIELD")).toBe(true);
332
- });
333
- it("should fail when server is missing required field: command", () => {
334
- const result = validateSkill({
335
- metadata: {
336
- name: "test",
337
- description: "test",
338
- requiresMcpServers: [
339
- {
340
- name: "test-server",
341
- description: "A test server"
342
- }
343
- ]
344
- },
345
- body: ""
346
- });
347
- expect(result.valid).toBe(false);
348
- expect(result.errors.some((e) => e.code === "MISSING_FIELD")).toBe(true);
349
- });
350
- it("should fail when server is missing required field: description", () => {
351
- const result = validateSkill({
352
- metadata: {
353
- name: "test",
354
- description: "test",
355
- requiresMcpServers: [
356
- {
357
- name: "test-server",
358
- command: "npx"
359
- }
360
- ]
361
- },
362
- body: ""
363
- });
364
- expect(result.valid).toBe(false);
365
- expect(result.errors.some((e) => e.code === "MISSING_FIELD")).toBe(true);
366
- });
367
- it.each([
368
- ["spaces", "test server"],
369
- ["special chars", "test@server"],
370
- ["consecutive hyphens", "test--server"],
371
- ["uppercase", "Test-Server"],
372
- ["leading hyphen", "-test-server"],
373
- ["trailing hyphen", "test-server-"],
374
- ["underscores", "test_server"],
375
- ["dots", "test.server"]
376
- ])("should fail for server name with %s", (_, name) => {
377
- const result = validateSkill({
378
- metadata: {
379
- name: "test",
380
- description: "test",
381
- requiresMcpServers: [
382
- {
383
- name,
384
- command: "npx",
385
- description: "A test server"
386
- }
387
- ]
388
- },
389
- body: ""
390
- });
391
- expect(result.valid).toBe(false);
392
- expect(result.errors.some((e) => e.code === "INVALID_NAME_FORMAT")).toBe(true);
393
- });
394
- it("should fail when server parameter is missing required field: description", () => {
395
- const result = validateSkill({
396
- metadata: {
397
- name: "test",
398
- description: "test",
399
- requiresMcpServers: [
400
- {
401
- name: "test-server",
402
- command: "npx",
403
- description: "A test server",
404
- args: ["{{API_KEY}}"],
405
- parameters: {
406
- "api-key": {
407
- required: true
408
- }
409
- }
410
- }
411
- ]
412
- },
413
- body: ""
414
- });
415
- expect(result.valid).toBe(false);
416
- expect(result.errors.some((e) => e.code === "MISSING_FIELD")).toBe(true);
417
- });
418
- it("should fail when server parameter is missing required field: required", () => {
419
- const result = validateSkill({
420
- metadata: {
421
- name: "test",
422
- description: "test",
423
- requiresMcpServers: [
424
- {
425
- name: "test-server",
426
- command: "npx",
427
- description: "A test server",
428
- args: ["{{API_KEY}}"],
429
- parameters: {
430
- "api-key": {
431
- description: "API key for authentication"
432
- }
433
- }
434
- }
435
- ]
436
- },
437
- body: ""
438
- });
439
- expect(result.valid).toBe(false);
440
- expect(result.errors.some((e) => e.code === "MISSING_FIELD")).toBe(true);
441
- });
442
- it("should fail when server parameter has invalid default value type", () => {
443
- const result = validateSkill({
444
- metadata: {
445
- name: "test",
446
- description: "test",
447
- requiresMcpServers: [
448
- {
449
- name: "test-server",
450
- command: "npx",
451
- description: "A test server",
452
- parameters: {
453
- "api-key": {
454
- description: "API key",
455
- required: false,
456
- default: 12345
457
- }
458
- }
459
- }
460
- ]
461
- },
462
- body: ""
463
- });
464
- expect(result.valid).toBe(false);
465
- expect(result.errors.some((e) => e.code === "INVALID_FIELD_TYPE")).toBe(true);
466
- });
467
- it("should validate a complete valid MCP server specification", () => {
468
- const result = validateSkill({
469
- metadata: {
470
- name: "test",
471
- description: "test",
472
- requiresMcpServers: [
473
- {
474
- name: "github-server",
475
- package: "@modelcontextprotocol/server-github",
476
- command: "npx",
477
- description: "GitHub MCP server for repository access",
478
- args: ["-y", "@modelcontextprotocol/server-github"],
479
- env: {
480
- GITHUB_TOKEN: "{{GITHUB_TOKEN}}"
481
- },
482
- parameters: {
483
- "github-token": {
484
- description: "GitHub personal access token",
485
- required: true,
486
- sensitive: true,
487
- example: "ghp_xxxxxxxxxxxx"
488
- }
489
- }
490
- }
491
- ]
492
- },
493
- body: ""
494
- });
495
- expect(result.valid).toBe(true);
496
- });
497
- it("should validate multiple servers in array", () => {
498
- const result = validateSkill({
499
- metadata: {
500
- name: "test",
501
- description: "test",
502
- requiresMcpServers: [
503
- {
504
- name: "server-one",
505
- command: "npx",
506
- description: "First server"
507
- },
508
- {
509
- name: "server-two",
510
- command: "node",
511
- description: "Second server",
512
- args: ["server.js"]
513
- },
514
- {
515
- name: "server-three",
516
- command: "python",
517
- description: "Third server",
518
- args: ["-m", "server"]
519
- }
520
- ]
521
- },
522
- body: ""
523
- });
524
- expect(result.valid).toBe(true);
525
- });
526
- it.each([
527
- ["camelCase", "apiKey"],
528
- ["PascalCase", "ApiKey"],
529
- ["snake_case", "api_key"],
530
- ["spaces", "api key"],
531
- ["uppercase", "API-KEY"],
532
- ["consecutive hyphens", "api--key"]
533
- ])("should fail for parameter name with %s", (_, paramName) => {
534
- const result = validateSkill({
535
- metadata: {
536
- name: "test",
537
- description: "test",
538
- requiresMcpServers: [
539
- {
540
- name: "test-server",
541
- command: "npx",
542
- description: "A test server",
543
- parameters: {
544
- [paramName]: {
545
- description: "Test parameter",
546
- required: true
547
- }
548
- }
549
- }
550
- ]
551
- },
552
- body: ""
553
- });
554
- expect(result.valid).toBe(false);
555
- expect(result.errors.some((e) => e.code === "INVALID_NAME_FORMAT")).toBe(true);
556
- });
557
- it("should fail when args is not an array", () => {
558
- const result = validateSkill({
559
- metadata: {
560
- name: "test",
561
- description: "test",
562
- requiresMcpServers: [
563
- {
564
- name: "test-server",
565
- command: "npx",
566
- description: "A test server",
567
- args: "not-an-array"
568
- }
569
- ]
570
- },
571
- body: ""
572
- });
573
- expect(result.valid).toBe(false);
574
- expect(result.errors.some((e) => e.code === "INVALID_FIELD_TYPE")).toBe(true);
575
- });
576
- it("should fail when env is not an object", () => {
577
- const result = validateSkill({
578
- metadata: {
579
- name: "test",
580
- description: "test",
581
- requiresMcpServers: [
582
- {
583
- name: "test-server",
584
- command: "npx",
585
- description: "A test server",
586
- env: "not-an-object"
587
- }
588
- ]
589
- },
590
- body: ""
591
- });
592
- expect(result.valid).toBe(false);
593
- expect(result.errors.some((e) => e.code === "INVALID_FIELD_TYPE")).toBe(true);
594
- });
595
- it("should fail when parameters is not an object", () => {
596
- const result = validateSkill({
597
- metadata: {
598
- name: "test",
599
- description: "test",
600
- requiresMcpServers: [
601
- {
602
- name: "test-server",
603
- command: "npx",
604
- description: "A test server",
605
- parameters: ["not", "an", "object"]
606
- }
607
- ]
608
- },
609
- body: ""
610
- });
611
- expect(result.valid).toBe(false);
612
- expect(result.errors.some((e) => e.code === "INVALID_FIELD_TYPE")).toBe(true);
613
- });
614
- it("should fail when server has additional properties", () => {
615
- const result = validateSkill({
616
- metadata: {
617
- name: "test",
618
- description: "test",
619
- requiresMcpServers: [
620
- {
621
- name: "test-server",
622
- command: "npx",
623
- description: "A test server",
624
- unexpectedField: "should not be here"
625
- }
626
- ]
627
- },
628
- body: ""
629
- });
630
- expect(result.valid).toBe(false);
631
- expect(result.errors.length).toBeGreaterThan(0);
632
- });
633
- it("should fail when parameter has additional properties", () => {
634
- const result = validateSkill({
635
- metadata: {
636
- name: "test",
637
- description: "test",
638
- requiresMcpServers: [
639
- {
640
- name: "test-server",
641
- command: "npx",
642
- description: "A test server",
643
- parameters: {
644
- "api-key": {
645
- description: "API key",
646
- required: true,
647
- unexpectedField: "should not be here"
648
- }
649
- }
650
- }
651
- ]
652
- },
653
- body: ""
654
- });
655
- expect(result.valid).toBe(false);
656
- expect(result.errors.length).toBeGreaterThan(0);
657
- });
658
- });
659
- });
660
- //# sourceMappingURL=validator.test.js.map