@codemcp/agentskills-cli 0.0.5
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/LICENSE +19 -0
- package/dist/__tests__/cli.test.d.ts +2 -0
- package/dist/__tests__/cli.test.d.ts.map +1 -0
- package/dist/__tests__/cli.test.js +210 -0
- package/dist/__tests__/cli.test.js.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +66 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/__tests__/add.test.d.ts +2 -0
- package/dist/commands/__tests__/add.test.d.ts.map +1 -0
- package/dist/commands/__tests__/add.test.js +363 -0
- package/dist/commands/__tests__/add.test.js.map +1 -0
- package/dist/commands/__tests__/install.test.d.ts +2 -0
- package/dist/commands/__tests__/install.test.d.ts.map +1 -0
- package/dist/commands/__tests__/install.test.js +587 -0
- package/dist/commands/__tests__/install.test.js.map +1 -0
- package/dist/commands/__tests__/list.test.d.ts +2 -0
- package/dist/commands/__tests__/list.test.d.ts.map +1 -0
- package/dist/commands/__tests__/list.test.js +69 -0
- package/dist/commands/__tests__/list.test.js.map +1 -0
- package/dist/commands/__tests__/validate.test.d.ts +2 -0
- package/dist/commands/__tests__/validate.test.d.ts.map +1 -0
- package/dist/commands/__tests__/validate.test.js +858 -0
- package/dist/commands/__tests__/validate.test.js.map +1 -0
- package/dist/commands/add.d.ts +28 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +65 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/install.d.ts +16 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +125 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/list.d.ts +4 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +41 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/validate.d.ts +26 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +244 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,858 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { tmpdir } from "os";
|
|
5
|
+
import { validateCommand } from "../validate.js";
|
|
6
|
+
/**
|
|
7
|
+
* Comprehensive test suite for validate command
|
|
8
|
+
*
|
|
9
|
+
* Following TDD approach:
|
|
10
|
+
* - Write tests first to define the command interface and behavior
|
|
11
|
+
* - Tests define expected user experience before implementation
|
|
12
|
+
* - Clear test structure with arrange-act-assert
|
|
13
|
+
* - Minimal mocking - use real file system with temp directories
|
|
14
|
+
*
|
|
15
|
+
* Coverage:
|
|
16
|
+
* 1. Single skill validation (valid, invalid parse, invalid validation, warnings)
|
|
17
|
+
* 2. Directory validation (all valid, some invalid, mixed, no skills)
|
|
18
|
+
* 3. Error handling (missing paths, no SKILL.md, permissions)
|
|
19
|
+
* 4. Output formatting (success, error, warning, summary messages)
|
|
20
|
+
* 5. --strict flag (treats warnings as errors)
|
|
21
|
+
* 6. --fix flag (stub for now, shows not implemented message)
|
|
22
|
+
*/
|
|
23
|
+
describe("validate command", () => {
|
|
24
|
+
let testDir;
|
|
25
|
+
let consoleLogSpy;
|
|
26
|
+
let consoleErrorSpy;
|
|
27
|
+
let processExitSpy;
|
|
28
|
+
beforeEach(async () => {
|
|
29
|
+
// Create unique temp directory for each test
|
|
30
|
+
testDir = join(tmpdir(), `agentskills-test-${Date.now()}-${Math.random()}`);
|
|
31
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
32
|
+
// Mock console and process.exit
|
|
33
|
+
consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => { });
|
|
34
|
+
consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
35
|
+
processExitSpy = vi
|
|
36
|
+
.spyOn(process, "exit")
|
|
37
|
+
.mockImplementation((() => { }));
|
|
38
|
+
});
|
|
39
|
+
afterEach(async () => {
|
|
40
|
+
// Clean up temp directory
|
|
41
|
+
try {
|
|
42
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
// Ignore cleanup errors
|
|
46
|
+
}
|
|
47
|
+
// Restore mocks
|
|
48
|
+
consoleLogSpy.mockRestore();
|
|
49
|
+
consoleErrorSpy.mockRestore();
|
|
50
|
+
processExitSpy.mockRestore();
|
|
51
|
+
});
|
|
52
|
+
// Helper function to create a skill file
|
|
53
|
+
async function createSkillFile(dir, content) {
|
|
54
|
+
const skillPath = join(dir, "SKILL.md");
|
|
55
|
+
await fs.writeFile(skillPath, content);
|
|
56
|
+
return skillPath;
|
|
57
|
+
}
|
|
58
|
+
// Helper function to create a valid skill (with proper description length to avoid warnings)
|
|
59
|
+
async function createValidSkill(dir, name = "test-skill") {
|
|
60
|
+
const content = `---
|
|
61
|
+
name: ${name}
|
|
62
|
+
description: A comprehensive test skill for validation with sufficient description length to avoid warnings
|
|
63
|
+
license: MIT
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
# Test Skill
|
|
67
|
+
|
|
68
|
+
This is a valid skill for testing with no validation warnings.
|
|
69
|
+
`;
|
|
70
|
+
return createSkillFile(dir, content);
|
|
71
|
+
}
|
|
72
|
+
// Helper function to create skill with parse error
|
|
73
|
+
async function createParseErrorSkill(dir) {
|
|
74
|
+
const content = `---
|
|
75
|
+
name: invalid-yaml
|
|
76
|
+
description: "Missing closing quote
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
# Invalid Skill
|
|
80
|
+
`;
|
|
81
|
+
return createSkillFile(dir, content);
|
|
82
|
+
}
|
|
83
|
+
// Helper function to create skill with validation error
|
|
84
|
+
async function createValidationErrorSkill(dir) {
|
|
85
|
+
const content = `---
|
|
86
|
+
name: ""
|
|
87
|
+
description: This skill has actual validation errors - empty name field
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
# Invalid Skill
|
|
91
|
+
|
|
92
|
+
This skill has validation errors (empty name).
|
|
93
|
+
`;
|
|
94
|
+
return createSkillFile(dir, content);
|
|
95
|
+
}
|
|
96
|
+
// Helper function to create skill with warnings
|
|
97
|
+
async function createWarningSkill(dir) {
|
|
98
|
+
const content = `---
|
|
99
|
+
name: warning-skill
|
|
100
|
+
description: Short
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
# Warning Skill
|
|
104
|
+
|
|
105
|
+
This skill has a very short description which should trigger a warning.
|
|
106
|
+
`;
|
|
107
|
+
return createSkillFile(dir, content);
|
|
108
|
+
}
|
|
109
|
+
describe("Single Skill Validation", () => {
|
|
110
|
+
describe("Valid skill", () => {
|
|
111
|
+
it("should display success message for valid skill", async () => {
|
|
112
|
+
// Arrange
|
|
113
|
+
const skillDir = join(testDir, "valid-skill");
|
|
114
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
115
|
+
await createValidSkill(skillDir);
|
|
116
|
+
// Act
|
|
117
|
+
await validateCommand(skillDir, {});
|
|
118
|
+
// Assert
|
|
119
|
+
expect(consoleLogSpy).toHaveBeenCalled();
|
|
120
|
+
const output = consoleLogSpy.mock.calls
|
|
121
|
+
.map((call) => call.join(" "))
|
|
122
|
+
.join("\n");
|
|
123
|
+
expect(output).toMatch(/✓.*test-skill.*valid/i);
|
|
124
|
+
expect(processExitSpy).toHaveBeenCalledWith(0);
|
|
125
|
+
});
|
|
126
|
+
it("should exit with code 0 for valid skill", async () => {
|
|
127
|
+
// Arrange
|
|
128
|
+
const skillDir = join(testDir, "valid-skill");
|
|
129
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
130
|
+
await createValidSkill(skillDir);
|
|
131
|
+
// Act
|
|
132
|
+
await validateCommand(skillDir, {});
|
|
133
|
+
// Assert
|
|
134
|
+
expect(processExitSpy).toHaveBeenCalledWith(0);
|
|
135
|
+
});
|
|
136
|
+
it("should validate when given direct path to SKILL.md", async () => {
|
|
137
|
+
// Arrange
|
|
138
|
+
const skillDir = join(testDir, "valid-skill");
|
|
139
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
140
|
+
const skillPath = await createValidSkill(skillDir);
|
|
141
|
+
// Act
|
|
142
|
+
await validateCommand(skillPath, {});
|
|
143
|
+
// Assert
|
|
144
|
+
expect(processExitSpy).toHaveBeenCalledWith(0);
|
|
145
|
+
const output = consoleLogSpy.mock.calls
|
|
146
|
+
.map((call) => call.join(" "))
|
|
147
|
+
.join("\n");
|
|
148
|
+
expect(output).toMatch(/✓.*test-skill.*valid/i);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
describe("Invalid skill - parse error", () => {
|
|
152
|
+
it("should display error message for parse error", async () => {
|
|
153
|
+
// Arrange
|
|
154
|
+
const skillDir = join(testDir, "invalid-parse");
|
|
155
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
156
|
+
await createParseErrorSkill(skillDir);
|
|
157
|
+
// Act
|
|
158
|
+
await validateCommand(skillDir, {});
|
|
159
|
+
// Assert
|
|
160
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
161
|
+
const output = consoleErrorSpy.mock.calls
|
|
162
|
+
.map((call) => call.join(" "))
|
|
163
|
+
.join("\n");
|
|
164
|
+
expect(output).toMatch(/✗/);
|
|
165
|
+
expect(output).toMatch(/failed/i);
|
|
166
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
167
|
+
});
|
|
168
|
+
it("should exit with code 1 for parse error", async () => {
|
|
169
|
+
// Arrange
|
|
170
|
+
const skillDir = join(testDir, "invalid-parse");
|
|
171
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
172
|
+
await createParseErrorSkill(skillDir);
|
|
173
|
+
// Act
|
|
174
|
+
await validateCommand(skillDir, {});
|
|
175
|
+
// Assert
|
|
176
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
177
|
+
});
|
|
178
|
+
it("should display detailed error information", async () => {
|
|
179
|
+
// Arrange
|
|
180
|
+
const skillDir = join(testDir, "invalid-parse");
|
|
181
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
182
|
+
await createParseErrorSkill(skillDir);
|
|
183
|
+
// Act
|
|
184
|
+
await validateCommand(skillDir, {});
|
|
185
|
+
// Assert
|
|
186
|
+
const output = consoleErrorSpy.mock.calls
|
|
187
|
+
.map((call) => call.join(" "))
|
|
188
|
+
.join("\n");
|
|
189
|
+
expect(output).toMatch(/yaml|parse|invalid/i);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
describe("Invalid skill - validation error", () => {
|
|
193
|
+
it("should display error message for validation error", async () => {
|
|
194
|
+
// Arrange
|
|
195
|
+
const skillDir = join(testDir, "invalid-validation");
|
|
196
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
197
|
+
await createValidationErrorSkill(skillDir);
|
|
198
|
+
// Act
|
|
199
|
+
await validateCommand(skillDir, {});
|
|
200
|
+
// Assert
|
|
201
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
202
|
+
const output = consoleErrorSpy.mock.calls
|
|
203
|
+
.map((call) => call.join(" "))
|
|
204
|
+
.join("\n");
|
|
205
|
+
expect(output).toMatch(/✗/);
|
|
206
|
+
expect(output).toMatch(/validation/i);
|
|
207
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
208
|
+
});
|
|
209
|
+
it("should display all validation errors", async () => {
|
|
210
|
+
// Arrange
|
|
211
|
+
const skillDir = join(testDir, "invalid-validation");
|
|
212
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
213
|
+
await createValidationErrorSkill(skillDir);
|
|
214
|
+
// Act
|
|
215
|
+
await validateCommand(skillDir, {});
|
|
216
|
+
// Assert
|
|
217
|
+
const output = consoleErrorSpy.mock.calls
|
|
218
|
+
.map((call) => call.join(" "))
|
|
219
|
+
.join("\n");
|
|
220
|
+
// Should show error details
|
|
221
|
+
expect(output.length).toBeGreaterThan(50);
|
|
222
|
+
});
|
|
223
|
+
it("should exit with code 1 for validation error", async () => {
|
|
224
|
+
// Arrange
|
|
225
|
+
const skillDir = join(testDir, "invalid-validation");
|
|
226
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
227
|
+
await createValidationErrorSkill(skillDir);
|
|
228
|
+
// Act
|
|
229
|
+
await validateCommand(skillDir, {});
|
|
230
|
+
// Assert
|
|
231
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
describe("Skill with warnings (no --strict)", () => {
|
|
235
|
+
it("should display warning message", async () => {
|
|
236
|
+
// Arrange
|
|
237
|
+
const skillDir = join(testDir, "warning-skill");
|
|
238
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
239
|
+
await createWarningSkill(skillDir);
|
|
240
|
+
// Act
|
|
241
|
+
await validateCommand(skillDir, {});
|
|
242
|
+
// Assert
|
|
243
|
+
expect(consoleLogSpy).toHaveBeenCalled();
|
|
244
|
+
const output = consoleLogSpy.mock.calls
|
|
245
|
+
.map((call) => call.join(" "))
|
|
246
|
+
.join("\n");
|
|
247
|
+
expect(output).toMatch(/⚠|warning/i);
|
|
248
|
+
});
|
|
249
|
+
it("should still show success with warnings", async () => {
|
|
250
|
+
// Arrange
|
|
251
|
+
const skillDir = join(testDir, "warning-skill");
|
|
252
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
253
|
+
await createWarningSkill(skillDir);
|
|
254
|
+
// Act
|
|
255
|
+
await validateCommand(skillDir, {});
|
|
256
|
+
// Assert
|
|
257
|
+
const output = consoleLogSpy.mock.calls
|
|
258
|
+
.map((call) => call.join(" "))
|
|
259
|
+
.join("\n");
|
|
260
|
+
expect(output).toMatch(/✓.*warning-skill/i);
|
|
261
|
+
expect(output).toMatch(/⚠|warning/i);
|
|
262
|
+
});
|
|
263
|
+
it("should exit with code 0 when warnings present but not strict", async () => {
|
|
264
|
+
// Arrange
|
|
265
|
+
const skillDir = join(testDir, "warning-skill");
|
|
266
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
267
|
+
await createWarningSkill(skillDir);
|
|
268
|
+
// Act
|
|
269
|
+
await validateCommand(skillDir, {});
|
|
270
|
+
// Assert
|
|
271
|
+
expect(processExitSpy).toHaveBeenCalledWith(0);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
describe("Skill with warnings (--strict mode)", () => {
|
|
275
|
+
it("should treat warnings as errors in strict mode", async () => {
|
|
276
|
+
// Arrange
|
|
277
|
+
const skillDir = join(testDir, "warning-skill");
|
|
278
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
279
|
+
await createWarningSkill(skillDir);
|
|
280
|
+
// Act
|
|
281
|
+
await validateCommand(skillDir, { strict: true });
|
|
282
|
+
// Assert
|
|
283
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
284
|
+
const output = consoleErrorSpy.mock.calls
|
|
285
|
+
.map((call) => call.join(" "))
|
|
286
|
+
.join("\n");
|
|
287
|
+
expect(output).toMatch(/✗/);
|
|
288
|
+
expect(output).toMatch(/warning|strict/i);
|
|
289
|
+
});
|
|
290
|
+
it("should exit with code 1 in strict mode when warnings present", async () => {
|
|
291
|
+
// Arrange
|
|
292
|
+
const skillDir = join(testDir, "warning-skill");
|
|
293
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
294
|
+
await createWarningSkill(skillDir);
|
|
295
|
+
// Act
|
|
296
|
+
await validateCommand(skillDir, { strict: true });
|
|
297
|
+
// Assert
|
|
298
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
299
|
+
});
|
|
300
|
+
it("should display warning details as errors in strict mode", async () => {
|
|
301
|
+
// Arrange
|
|
302
|
+
const skillDir = join(testDir, "warning-skill");
|
|
303
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
304
|
+
await createWarningSkill(skillDir);
|
|
305
|
+
// Act
|
|
306
|
+
await validateCommand(skillDir, { strict: true });
|
|
307
|
+
// Assert
|
|
308
|
+
const output = consoleErrorSpy.mock.calls
|
|
309
|
+
.map((call) => call.join(" "))
|
|
310
|
+
.join("\n");
|
|
311
|
+
expect(output).toMatch(/⚠|warning/i);
|
|
312
|
+
expect(output).toMatch(/description/i);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
describe("Directory Validation (All Skills)", () => {
|
|
317
|
+
describe("All valid skills", () => {
|
|
318
|
+
it("should validate all skills in directory", async () => {
|
|
319
|
+
// Arrange
|
|
320
|
+
const skill1 = join(testDir, "skill-1");
|
|
321
|
+
const skill2 = join(testDir, "skill-2");
|
|
322
|
+
await fs.mkdir(skill1, { recursive: true });
|
|
323
|
+
await fs.mkdir(skill2, { recursive: true });
|
|
324
|
+
await createValidSkill(skill1, "skill-one");
|
|
325
|
+
await createValidSkill(skill2, "skill-two");
|
|
326
|
+
// Act
|
|
327
|
+
await validateCommand(testDir, {});
|
|
328
|
+
// Assert
|
|
329
|
+
const output = consoleLogSpy.mock.calls
|
|
330
|
+
.map((call) => call.join(" "))
|
|
331
|
+
.join("\n");
|
|
332
|
+
expect(output).toMatch(/skill-one/i);
|
|
333
|
+
expect(output).toMatch(/skill-two/i);
|
|
334
|
+
expect(processExitSpy).toHaveBeenCalledWith(0);
|
|
335
|
+
});
|
|
336
|
+
it("should display success summary for all valid", async () => {
|
|
337
|
+
// Arrange
|
|
338
|
+
const skill1 = join(testDir, "skill-1");
|
|
339
|
+
const skill2 = join(testDir, "skill-2");
|
|
340
|
+
await fs.mkdir(skill1, { recursive: true });
|
|
341
|
+
await fs.mkdir(skill2, { recursive: true });
|
|
342
|
+
await createValidSkill(skill1, "skill-one");
|
|
343
|
+
await createValidSkill(skill2, "skill-two");
|
|
344
|
+
// Act
|
|
345
|
+
await validateCommand(testDir, {});
|
|
346
|
+
// Assert
|
|
347
|
+
const output = consoleLogSpy.mock.calls
|
|
348
|
+
.map((call) => call.join(" "))
|
|
349
|
+
.join("\n");
|
|
350
|
+
expect(output).toMatch(/validated.*2.*skills/i);
|
|
351
|
+
expect(output).toMatch(/2.*valid/i);
|
|
352
|
+
expect(output).toMatch(/0.*invalid/i);
|
|
353
|
+
});
|
|
354
|
+
it("should exit with code 0 when all valid", async () => {
|
|
355
|
+
// Arrange
|
|
356
|
+
const skill1 = join(testDir, "skill-1");
|
|
357
|
+
const skill2 = join(testDir, "skill-2");
|
|
358
|
+
await fs.mkdir(skill1, { recursive: true });
|
|
359
|
+
await fs.mkdir(skill2, { recursive: true });
|
|
360
|
+
await createValidSkill(skill1, "skill-one");
|
|
361
|
+
await createValidSkill(skill2, "skill-two");
|
|
362
|
+
// Act
|
|
363
|
+
await validateCommand(testDir, {});
|
|
364
|
+
// Assert
|
|
365
|
+
expect(processExitSpy).toHaveBeenCalledWith(0);
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
describe("Some invalid skills", () => {
|
|
369
|
+
it("should validate all and report invalid ones", async () => {
|
|
370
|
+
// Arrange
|
|
371
|
+
const skill1 = join(testDir, "skill-1");
|
|
372
|
+
const skill2 = join(testDir, "skill-2");
|
|
373
|
+
await fs.mkdir(skill1, { recursive: true });
|
|
374
|
+
await fs.mkdir(skill2, { recursive: true });
|
|
375
|
+
await createValidSkill(skill1, "skill-one");
|
|
376
|
+
await createParseErrorSkill(skill2);
|
|
377
|
+
// Act
|
|
378
|
+
await validateCommand(testDir, {});
|
|
379
|
+
// Assert
|
|
380
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
381
|
+
const output = consoleErrorSpy.mock.calls
|
|
382
|
+
.map((call) => call.join(" "))
|
|
383
|
+
.join("\n");
|
|
384
|
+
expect(output).toMatch(/✗/);
|
|
385
|
+
});
|
|
386
|
+
it("should display summary with failure count", async () => {
|
|
387
|
+
// Arrange
|
|
388
|
+
const skill1 = join(testDir, "skill-1");
|
|
389
|
+
const skill2 = join(testDir, "skill-2");
|
|
390
|
+
await fs.mkdir(skill1, { recursive: true });
|
|
391
|
+
await fs.mkdir(skill2, { recursive: true });
|
|
392
|
+
await createValidSkill(skill1, "skill-one");
|
|
393
|
+
await createParseErrorSkill(skill2);
|
|
394
|
+
// Act
|
|
395
|
+
await validateCommand(testDir, {});
|
|
396
|
+
// Assert
|
|
397
|
+
const allOutput = [
|
|
398
|
+
...consoleLogSpy.mock.calls.map((call) => call.join(" ")),
|
|
399
|
+
...consoleErrorSpy.mock.calls.map((call) => call.join(" "))
|
|
400
|
+
].join("\n");
|
|
401
|
+
expect(allOutput).toMatch(/validated.*2.*skills/i);
|
|
402
|
+
expect(allOutput).toMatch(/1.*valid/i);
|
|
403
|
+
expect(allOutput).toMatch(/1.*invalid/i);
|
|
404
|
+
});
|
|
405
|
+
it("should exit with code 1 when some invalid", async () => {
|
|
406
|
+
// Arrange
|
|
407
|
+
const skill1 = join(testDir, "skill-1");
|
|
408
|
+
const skill2 = join(testDir, "skill-2");
|
|
409
|
+
await fs.mkdir(skill1, { recursive: true });
|
|
410
|
+
await fs.mkdir(skill2, { recursive: true });
|
|
411
|
+
await createValidSkill(skill1, "skill-one");
|
|
412
|
+
await createParseErrorSkill(skill2);
|
|
413
|
+
// Act
|
|
414
|
+
await validateCommand(testDir, {});
|
|
415
|
+
// Assert
|
|
416
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
describe("Mix of valid, invalid, and warnings", () => {
|
|
420
|
+
it("should report detailed results for mixed scenarios", async () => {
|
|
421
|
+
// Arrange
|
|
422
|
+
const skill1 = join(testDir, "skill-1");
|
|
423
|
+
const skill2 = join(testDir, "skill-2");
|
|
424
|
+
const skill3 = join(testDir, "skill-3");
|
|
425
|
+
await fs.mkdir(skill1, { recursive: true });
|
|
426
|
+
await fs.mkdir(skill2, { recursive: true });
|
|
427
|
+
await fs.mkdir(skill3, { recursive: true });
|
|
428
|
+
await createValidSkill(skill1, "skill-one");
|
|
429
|
+
await createWarningSkill(skill2);
|
|
430
|
+
await createParseErrorSkill(skill3);
|
|
431
|
+
// Act
|
|
432
|
+
await validateCommand(testDir, {});
|
|
433
|
+
// Assert
|
|
434
|
+
const allOutput = [
|
|
435
|
+
...consoleLogSpy.mock.calls.map((call) => call.join(" ")),
|
|
436
|
+
...consoleErrorSpy.mock.calls.map((call) => call.join(" "))
|
|
437
|
+
].join("\n");
|
|
438
|
+
expect(allOutput).toMatch(/✓/); // Valid skill
|
|
439
|
+
expect(allOutput).toMatch(/⚠/); // Warning
|
|
440
|
+
expect(allOutput).toMatch(/✗/); // Error
|
|
441
|
+
});
|
|
442
|
+
it("should exit with code 1 when any invalid present", async () => {
|
|
443
|
+
// Arrange
|
|
444
|
+
const skill1 = join(testDir, "skill-1");
|
|
445
|
+
const skill2 = join(testDir, "skill-2");
|
|
446
|
+
const skill3 = join(testDir, "skill-3");
|
|
447
|
+
await fs.mkdir(skill1, { recursive: true });
|
|
448
|
+
await fs.mkdir(skill2, { recursive: true });
|
|
449
|
+
await fs.mkdir(skill3, { recursive: true });
|
|
450
|
+
await createValidSkill(skill1, "skill-one");
|
|
451
|
+
await createWarningSkill(skill2);
|
|
452
|
+
await createParseErrorSkill(skill3);
|
|
453
|
+
// Act
|
|
454
|
+
await validateCommand(testDir, {});
|
|
455
|
+
// Assert
|
|
456
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
457
|
+
});
|
|
458
|
+
it("should treat warnings as errors in strict mode for directory", async () => {
|
|
459
|
+
// Arrange
|
|
460
|
+
const skill1 = join(testDir, "skill-1");
|
|
461
|
+
const skill2 = join(testDir, "skill-2");
|
|
462
|
+
await fs.mkdir(skill1, { recursive: true });
|
|
463
|
+
await fs.mkdir(skill2, { recursive: true });
|
|
464
|
+
await createValidSkill(skill1, "skill-one");
|
|
465
|
+
await createWarningSkill(skill2);
|
|
466
|
+
// Act
|
|
467
|
+
await validateCommand(testDir, { strict: true });
|
|
468
|
+
// Assert
|
|
469
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
470
|
+
const allOutput = [
|
|
471
|
+
...consoleLogSpy.mock.calls.map((call) => call.join(" ")),
|
|
472
|
+
...consoleErrorSpy.mock.calls.map((call) => call.join(" "))
|
|
473
|
+
].join("\n");
|
|
474
|
+
expect(allOutput).toMatch(/1.*valid/i);
|
|
475
|
+
expect(allOutput).toMatch(/1.*invalid/i);
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
describe("No skills found", () => {
|
|
479
|
+
it("should display appropriate message when no skills found", async () => {
|
|
480
|
+
// Arrange - empty directory
|
|
481
|
+
const emptyDir = join(testDir, "empty");
|
|
482
|
+
await fs.mkdir(emptyDir, { recursive: true });
|
|
483
|
+
// Act
|
|
484
|
+
await validateCommand(emptyDir, {});
|
|
485
|
+
// Assert
|
|
486
|
+
expect(consoleLogSpy).toHaveBeenCalled();
|
|
487
|
+
const output = consoleLogSpy.mock.calls
|
|
488
|
+
.map((call) => call.join(" "))
|
|
489
|
+
.join("\n");
|
|
490
|
+
expect(output).toMatch(/no skills found/i);
|
|
491
|
+
});
|
|
492
|
+
it("should exit with code 0 when no skills found", async () => {
|
|
493
|
+
// Arrange - empty directory
|
|
494
|
+
const emptyDir = join(testDir, "empty");
|
|
495
|
+
await fs.mkdir(emptyDir, { recursive: true });
|
|
496
|
+
// Act
|
|
497
|
+
await validateCommand(emptyDir, {});
|
|
498
|
+
// Assert
|
|
499
|
+
expect(processExitSpy).toHaveBeenCalledWith(0);
|
|
500
|
+
});
|
|
501
|
+
it("should handle directory with only non-skill files", async () => {
|
|
502
|
+
// Arrange
|
|
503
|
+
const dir = join(testDir, "non-skills");
|
|
504
|
+
await fs.mkdir(dir, { recursive: true });
|
|
505
|
+
await fs.writeFile(join(dir, "README.md"), "# Not a skill");
|
|
506
|
+
await fs.writeFile(join(dir, "notes.txt"), "Some notes");
|
|
507
|
+
// Act
|
|
508
|
+
await validateCommand(dir, {});
|
|
509
|
+
// Assert
|
|
510
|
+
const output = consoleLogSpy.mock.calls
|
|
511
|
+
.map((call) => call.join(" "))
|
|
512
|
+
.join("\n");
|
|
513
|
+
expect(output).toMatch(/no skills found/i);
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
describe("Error Handling", () => {
|
|
518
|
+
describe("Path does not exist", () => {
|
|
519
|
+
it("should display error message for non-existent path", async () => {
|
|
520
|
+
// Arrange
|
|
521
|
+
const nonExistentPath = join(testDir, "does-not-exist");
|
|
522
|
+
// Act
|
|
523
|
+
await validateCommand(nonExistentPath, {});
|
|
524
|
+
// Assert
|
|
525
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
526
|
+
const output = consoleErrorSpy.mock.calls
|
|
527
|
+
.map((call) => call.join(" "))
|
|
528
|
+
.join("\n");
|
|
529
|
+
expect(output).toMatch(/not found|does not exist/i);
|
|
530
|
+
});
|
|
531
|
+
it("should exit with code 1 for non-existent path", async () => {
|
|
532
|
+
// Arrange
|
|
533
|
+
const nonExistentPath = join(testDir, "does-not-exist");
|
|
534
|
+
// Act
|
|
535
|
+
await validateCommand(nonExistentPath, {});
|
|
536
|
+
// Assert
|
|
537
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
describe("Not a skill directory (no SKILL.md)", () => {
|
|
541
|
+
it("should display error when directory has no SKILL.md", async () => {
|
|
542
|
+
// Arrange
|
|
543
|
+
const noSkillDir = join(testDir, "no-skill");
|
|
544
|
+
await fs.mkdir(noSkillDir, { recursive: true });
|
|
545
|
+
await fs.writeFile(join(noSkillDir, "README.md"), "# Not a skill");
|
|
546
|
+
// Act
|
|
547
|
+
await validateCommand(noSkillDir, {});
|
|
548
|
+
// Assert
|
|
549
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
550
|
+
const output = consoleErrorSpy.mock.calls
|
|
551
|
+
.map((call) => call.join(" "))
|
|
552
|
+
.join("\n");
|
|
553
|
+
expect(output).toMatch(/no.*skill\.md|not.*skill/i);
|
|
554
|
+
});
|
|
555
|
+
it("should exit with code 1 when no SKILL.md found", async () => {
|
|
556
|
+
// Arrange
|
|
557
|
+
const noSkillDir = join(testDir, "no-skill");
|
|
558
|
+
await fs.mkdir(noSkillDir, { recursive: true });
|
|
559
|
+
await fs.writeFile(join(noSkillDir, "README.md"), "# Not a skill");
|
|
560
|
+
// Act
|
|
561
|
+
await validateCommand(noSkillDir, {});
|
|
562
|
+
// Assert
|
|
563
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
describe("Permission errors", () => {
|
|
567
|
+
it("should handle permission errors gracefully", async () => {
|
|
568
|
+
// Arrange
|
|
569
|
+
const restrictedDir = join(testDir, "restricted");
|
|
570
|
+
await fs.mkdir(restrictedDir, { recursive: true });
|
|
571
|
+
await createValidSkill(restrictedDir);
|
|
572
|
+
// Make directory unreadable (Unix-like systems only)
|
|
573
|
+
if (process.platform !== "win32") {
|
|
574
|
+
await fs.chmod(restrictedDir, 0o000);
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
// Skip on Windows
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
// Act
|
|
581
|
+
try {
|
|
582
|
+
await validateCommand(restrictedDir, {});
|
|
583
|
+
// Assert
|
|
584
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
585
|
+
const output = consoleErrorSpy.mock.calls
|
|
586
|
+
.map((call) => call.join(" "))
|
|
587
|
+
.join("\n");
|
|
588
|
+
expect(output).toMatch(/permission|access/i);
|
|
589
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
590
|
+
}
|
|
591
|
+
finally {
|
|
592
|
+
// Restore permissions for cleanup
|
|
593
|
+
await fs.chmod(restrictedDir, 0o755);
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
describe("No path provided (should validate default locations)", () => {
|
|
598
|
+
it("should validate skills in default locations when no path provided", async () => {
|
|
599
|
+
// This test requires mocking config to provide default locations
|
|
600
|
+
// For now, we'll test that it doesn't crash
|
|
601
|
+
// Act
|
|
602
|
+
await validateCommand(undefined, {});
|
|
603
|
+
// Assert
|
|
604
|
+
// Should either find skills or report none found, but not crash
|
|
605
|
+
expect(processExitSpy).toHaveBeenCalled();
|
|
606
|
+
});
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
describe("Output Formatting", () => {
|
|
610
|
+
describe("Success format", () => {
|
|
611
|
+
it("should use green checkmark for success", async () => {
|
|
612
|
+
// Arrange
|
|
613
|
+
const skillDir = join(testDir, "valid-skill");
|
|
614
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
615
|
+
await createValidSkill(skillDir);
|
|
616
|
+
// Act
|
|
617
|
+
await validateCommand(skillDir, {});
|
|
618
|
+
// Assert
|
|
619
|
+
const output = consoleLogSpy.mock.calls
|
|
620
|
+
.map((call) => call.join(" "))
|
|
621
|
+
.join("\n");
|
|
622
|
+
expect(output).toMatch(/✓/);
|
|
623
|
+
});
|
|
624
|
+
it("should include skill name in success message", async () => {
|
|
625
|
+
// Arrange
|
|
626
|
+
const skillDir = join(testDir, "valid-skill");
|
|
627
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
628
|
+
await createValidSkill(skillDir, "my-awesome-skill");
|
|
629
|
+
// Act
|
|
630
|
+
await validateCommand(skillDir, {});
|
|
631
|
+
// Assert
|
|
632
|
+
const output = consoleLogSpy.mock.calls
|
|
633
|
+
.map((call) => call.join(" "))
|
|
634
|
+
.join("\n");
|
|
635
|
+
expect(output).toMatch(/my-awesome-skill/);
|
|
636
|
+
expect(output).toMatch(/valid/i);
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
describe("Error format", () => {
|
|
640
|
+
it("should use red X for errors", async () => {
|
|
641
|
+
// Arrange
|
|
642
|
+
const skillDir = join(testDir, "invalid-skill");
|
|
643
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
644
|
+
await createParseErrorSkill(skillDir);
|
|
645
|
+
// Act
|
|
646
|
+
await validateCommand(skillDir, {});
|
|
647
|
+
// Assert
|
|
648
|
+
const output = consoleErrorSpy.mock.calls
|
|
649
|
+
.map((call) => call.join(" "))
|
|
650
|
+
.join("\n");
|
|
651
|
+
expect(output).toMatch(/✗/);
|
|
652
|
+
});
|
|
653
|
+
it("should include error details with indentation", async () => {
|
|
654
|
+
// Arrange
|
|
655
|
+
const skillDir = join(testDir, "invalid-skill");
|
|
656
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
657
|
+
await createParseErrorSkill(skillDir);
|
|
658
|
+
// Act
|
|
659
|
+
await validateCommand(skillDir, {});
|
|
660
|
+
// Assert
|
|
661
|
+
const output = consoleErrorSpy.mock.calls
|
|
662
|
+
.map((call) => call.join(" "))
|
|
663
|
+
.join("\n");
|
|
664
|
+
// Should have indented error details (spaces or dashes)
|
|
665
|
+
expect(output).toMatch(/\s{2,}| {2}-/);
|
|
666
|
+
});
|
|
667
|
+
});
|
|
668
|
+
describe("Warning format", () => {
|
|
669
|
+
it("should use yellow warning symbol for warnings", async () => {
|
|
670
|
+
// Arrange
|
|
671
|
+
const skillDir = join(testDir, "warning-skill");
|
|
672
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
673
|
+
await createWarningSkill(skillDir);
|
|
674
|
+
// Act
|
|
675
|
+
await validateCommand(skillDir, {});
|
|
676
|
+
// Assert
|
|
677
|
+
const output = consoleLogSpy.mock.calls
|
|
678
|
+
.map((call) => call.join(" "))
|
|
679
|
+
.join("\n");
|
|
680
|
+
expect(output).toMatch(/⚠/);
|
|
681
|
+
});
|
|
682
|
+
it("should include warning description", async () => {
|
|
683
|
+
// Arrange
|
|
684
|
+
const skillDir = join(testDir, "warning-skill");
|
|
685
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
686
|
+
await createWarningSkill(skillDir);
|
|
687
|
+
// Act
|
|
688
|
+
await validateCommand(skillDir, {});
|
|
689
|
+
// Assert
|
|
690
|
+
const output = consoleLogSpy.mock.calls
|
|
691
|
+
.map((call) => call.join(" "))
|
|
692
|
+
.join("\n");
|
|
693
|
+
expect(output).toMatch(/warning/i);
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
describe("Summary format", () => {
|
|
697
|
+
it("should display summary with skill counts", async () => {
|
|
698
|
+
// Arrange
|
|
699
|
+
const skill1 = join(testDir, "skill-1");
|
|
700
|
+
const skill2 = join(testDir, "skill-2");
|
|
701
|
+
await fs.mkdir(skill1, { recursive: true });
|
|
702
|
+
await fs.mkdir(skill2, { recursive: true });
|
|
703
|
+
await createValidSkill(skill1);
|
|
704
|
+
await createValidSkill(skill2);
|
|
705
|
+
// Act
|
|
706
|
+
await validateCommand(testDir, {});
|
|
707
|
+
// Assert
|
|
708
|
+
const output = consoleLogSpy.mock.calls
|
|
709
|
+
.map((call) => call.join(" "))
|
|
710
|
+
.join("\n");
|
|
711
|
+
expect(output).toMatch(/validated.*2.*skills/i);
|
|
712
|
+
expect(output).toMatch(/2.*valid/i);
|
|
713
|
+
expect(output).toMatch(/0.*invalid/i);
|
|
714
|
+
});
|
|
715
|
+
it("should use proper pluralization for summary", async () => {
|
|
716
|
+
// Arrange - single skill
|
|
717
|
+
const skill1 = join(testDir, "skill-1");
|
|
718
|
+
await fs.mkdir(skill1, { recursive: true });
|
|
719
|
+
await createValidSkill(skill1);
|
|
720
|
+
// Act
|
|
721
|
+
await validateCommand(testDir, {});
|
|
722
|
+
// Assert
|
|
723
|
+
const output = consoleLogSpy.mock.calls
|
|
724
|
+
.map((call) => call.join(" "))
|
|
725
|
+
.join("\n");
|
|
726
|
+
// Should handle singular properly
|
|
727
|
+
expect(output).toMatch(/1.*skill|skill[^s]|1.*valid/i);
|
|
728
|
+
});
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
describe("--fix Flag", () => {
|
|
732
|
+
describe("Not implemented (stub)", () => {
|
|
733
|
+
it("should display not implemented message when --fix used", async () => {
|
|
734
|
+
// Arrange
|
|
735
|
+
const skillDir = join(testDir, "valid-skill");
|
|
736
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
737
|
+
await createValidSkill(skillDir);
|
|
738
|
+
// Act
|
|
739
|
+
await validateCommand(skillDir, { fix: true });
|
|
740
|
+
// Assert
|
|
741
|
+
expect(consoleLogSpy).toHaveBeenCalled();
|
|
742
|
+
const output = consoleLogSpy.mock.calls
|
|
743
|
+
.map((call) => call.join(" "))
|
|
744
|
+
.join("\n");
|
|
745
|
+
expect(output).toMatch(/auto-fix.*not.*implemented|--fix.*not.*available/i);
|
|
746
|
+
});
|
|
747
|
+
it("should still validate when --fix flag is used", async () => {
|
|
748
|
+
// Arrange
|
|
749
|
+
const skillDir = join(testDir, "valid-skill");
|
|
750
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
751
|
+
await createValidSkill(skillDir);
|
|
752
|
+
// Act
|
|
753
|
+
await validateCommand(skillDir, { fix: true });
|
|
754
|
+
// Assert
|
|
755
|
+
const output = consoleLogSpy.mock.calls
|
|
756
|
+
.map((call) => call.join(" "))
|
|
757
|
+
.join("\n");
|
|
758
|
+
expect(output).toMatch(/✓.*test-skill/i);
|
|
759
|
+
expect(processExitSpy).toHaveBeenCalledWith(0);
|
|
760
|
+
});
|
|
761
|
+
it("should report issues even with --fix flag", async () => {
|
|
762
|
+
// Arrange
|
|
763
|
+
const skillDir = join(testDir, "invalid-skill");
|
|
764
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
765
|
+
await createParseErrorSkill(skillDir);
|
|
766
|
+
// Act
|
|
767
|
+
await validateCommand(skillDir, { fix: true });
|
|
768
|
+
// Assert
|
|
769
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
770
|
+
const output = consoleErrorSpy.mock.calls
|
|
771
|
+
.map((call) => call.join(" "))
|
|
772
|
+
.join("\n");
|
|
773
|
+
expect(output).toMatch(/✗/);
|
|
774
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
775
|
+
});
|
|
776
|
+
});
|
|
777
|
+
});
|
|
778
|
+
describe("Combined Flags", () => {
|
|
779
|
+
it("should handle --strict and --fix together", async () => {
|
|
780
|
+
// Arrange
|
|
781
|
+
const skillDir = join(testDir, "warning-skill");
|
|
782
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
783
|
+
await createWarningSkill(skillDir);
|
|
784
|
+
// Act
|
|
785
|
+
await validateCommand(skillDir, { strict: true, fix: true });
|
|
786
|
+
// Assert
|
|
787
|
+
const allOutput = [
|
|
788
|
+
...consoleLogSpy.mock.calls.map((call) => call.join(" ")),
|
|
789
|
+
...consoleErrorSpy.mock.calls.map((call) => call.join(" "))
|
|
790
|
+
].join("\n");
|
|
791
|
+
expect(allOutput).toMatch(/auto-fix.*not.*implemented/i);
|
|
792
|
+
expect(processExitSpy).toHaveBeenCalledWith(1); // Should fail due to strict mode
|
|
793
|
+
});
|
|
794
|
+
});
|
|
795
|
+
describe("Edge Cases", () => {
|
|
796
|
+
it("should handle skill with very long content", async () => {
|
|
797
|
+
// Arrange
|
|
798
|
+
const skillDir = join(testDir, "long-skill");
|
|
799
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
800
|
+
const longContent = `---
|
|
801
|
+
name: long-skill
|
|
802
|
+
description: A skill with very long content
|
|
803
|
+
---
|
|
804
|
+
|
|
805
|
+
# Long Skill
|
|
806
|
+
|
|
807
|
+
${"Lorem ipsum dolor sit amet. ".repeat(1000)}
|
|
808
|
+
`;
|
|
809
|
+
await createSkillFile(skillDir, longContent);
|
|
810
|
+
// Act
|
|
811
|
+
await validateCommand(skillDir, {});
|
|
812
|
+
// Assert - should handle without crashing
|
|
813
|
+
expect(processExitSpy).toHaveBeenCalled();
|
|
814
|
+
});
|
|
815
|
+
it("should handle skill with special characters in description", async () => {
|
|
816
|
+
// Arrange
|
|
817
|
+
const skillDir = join(testDir, "special-skill");
|
|
818
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
819
|
+
const content = `---
|
|
820
|
+
name: skill-with-special-chars
|
|
821
|
+
description: Testing special characters émojis 🚀 and unicode 日本語 in skill description field
|
|
822
|
+
license: MIT
|
|
823
|
+
---
|
|
824
|
+
|
|
825
|
+
# Special Skill
|
|
826
|
+
|
|
827
|
+
This tests special characters in descriptions are allowed.
|
|
828
|
+
`;
|
|
829
|
+
await createSkillFile(skillDir, content);
|
|
830
|
+
// Act
|
|
831
|
+
await validateCommand(skillDir, {});
|
|
832
|
+
// Assert
|
|
833
|
+
const output = consoleLogSpy.mock.calls
|
|
834
|
+
.map((call) => call.join(" "))
|
|
835
|
+
.join("\n");
|
|
836
|
+
expect(output).toMatch(/skill-with-special-chars/);
|
|
837
|
+
expect(output).toMatch(/valid/);
|
|
838
|
+
});
|
|
839
|
+
it("should handle nested directory structures when validating all", async () => {
|
|
840
|
+
// Arrange
|
|
841
|
+
const nested1 = join(testDir, "category1", "skill-1");
|
|
842
|
+
const nested2 = join(testDir, "category2", "skill-2");
|
|
843
|
+
await fs.mkdir(nested1, { recursive: true });
|
|
844
|
+
await fs.mkdir(nested2, { recursive: true });
|
|
845
|
+
await createValidSkill(nested1, "nested-skill-1");
|
|
846
|
+
await createValidSkill(nested2, "nested-skill-2");
|
|
847
|
+
// Act
|
|
848
|
+
await validateCommand(testDir, {});
|
|
849
|
+
// Assert
|
|
850
|
+
const output = consoleLogSpy.mock.calls
|
|
851
|
+
.map((call) => call.join(" "))
|
|
852
|
+
.join("\n");
|
|
853
|
+
expect(output).toMatch(/nested-skill-1/);
|
|
854
|
+
expect(output).toMatch(/nested-skill-2/);
|
|
855
|
+
});
|
|
856
|
+
});
|
|
857
|
+
});
|
|
858
|
+
//# sourceMappingURL=validate.test.js.map
|