claude-skills-cli 0.0.20 → 0.0.21

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 (81) hide show
  1. package/dist/add-hook.cmd-B6iZtoPi.js +193 -0
  2. package/dist/add-hook.cmd-B6iZtoPi.js.map +1 -0
  3. package/dist/doctor.cmd-CkNw6ine.js +119 -0
  4. package/dist/doctor.cmd-CkNw6ine.js.map +1 -0
  5. package/dist/frontmatter-validator-DO686mla.js +226 -0
  6. package/dist/frontmatter-validator-DO686mla.js.map +1 -0
  7. package/dist/fs-CuGv3Ob2.js +23 -0
  8. package/dist/fs-CuGv3Ob2.js.map +1 -0
  9. package/dist/index.js +24 -22
  10. package/dist/index.js.map +1 -1
  11. package/dist/init.cmd-BoeuCgQP.js +108 -0
  12. package/dist/init.cmd-BoeuCgQP.js.map +1 -0
  13. package/dist/install.cmd-CH7yZ92g.js +79 -0
  14. package/dist/install.cmd-CH7yZ92g.js.map +1 -0
  15. package/dist/output-Dz8fk6Gu.js +102 -0
  16. package/dist/output-Dz8fk6Gu.js.map +1 -0
  17. package/dist/package.cmd-CwGRHdEq.js +107 -0
  18. package/dist/package.cmd-CwGRHdEq.js.map +1 -0
  19. package/dist/stats.cmd-D1ujNiDO.js +121 -0
  20. package/dist/stats.cmd-D1ujNiDO.js.map +1 -0
  21. package/dist/{core/templates.js → templates-BQTgkXfH.js} +16 -13
  22. package/dist/templates-BQTgkXfH.js.map +1 -0
  23. package/dist/validate.cmd-CDUJDKGs.js +96 -0
  24. package/dist/validate.cmd-CDUJDKGs.js.map +1 -0
  25. package/dist/validator-DV5zeeel.js +721 -0
  26. package/dist/validator-DV5zeeel.js.map +1 -0
  27. package/package.json +34 -35
  28. package/dist/commands/add-hook.cmd.js +0 -35
  29. package/dist/commands/add-hook.cmd.js.map +0 -1
  30. package/dist/commands/add-hook.js +0 -216
  31. package/dist/commands/add-hook.js.map +0 -1
  32. package/dist/commands/doctor.cmd.js +0 -19
  33. package/dist/commands/doctor.cmd.js.map +0 -1
  34. package/dist/commands/doctor.js +0 -128
  35. package/dist/commands/doctor.js.map +0 -1
  36. package/dist/commands/init.cmd.js +0 -37
  37. package/dist/commands/init.cmd.js.map +0 -1
  38. package/dist/commands/init.js +0 -86
  39. package/dist/commands/init.js.map +0 -1
  40. package/dist/commands/install.cmd.js +0 -23
  41. package/dist/commands/install.cmd.js.map +0 -1
  42. package/dist/commands/install.js +0 -64
  43. package/dist/commands/install.js.map +0 -1
  44. package/dist/commands/package.cmd.js +0 -28
  45. package/dist/commands/package.cmd.js.map +0 -1
  46. package/dist/commands/package.js +0 -134
  47. package/dist/commands/package.js.map +0 -1
  48. package/dist/commands/stats.cmd.js +0 -19
  49. package/dist/commands/stats.cmd.js.map +0 -1
  50. package/dist/commands/stats.js +0 -154
  51. package/dist/commands/stats.js.map +0 -1
  52. package/dist/commands/validate.cmd.js +0 -39
  53. package/dist/commands/validate.cmd.js.map +0 -1
  54. package/dist/commands/validate.js +0 -77
  55. package/dist/commands/validate.js.map +0 -1
  56. package/dist/core/templates.js.map +0 -1
  57. package/dist/core/validator.js +0 -252
  58. package/dist/core/validator.js.map +0 -1
  59. package/dist/help.js +0 -305
  60. package/dist/help.js.map +0 -1
  61. package/dist/skills/.gitkeep +0 -0
  62. package/dist/types.js +0 -2
  63. package/dist/types.js.map +0 -1
  64. package/dist/utils/fs.js +0 -25
  65. package/dist/utils/fs.js.map +0 -1
  66. package/dist/utils/output.js +0 -102
  67. package/dist/utils/output.js.map +0 -1
  68. package/dist/validators/alignment-validator.js +0 -54
  69. package/dist/validators/alignment-validator.js.map +0 -1
  70. package/dist/validators/content-validator.js +0 -156
  71. package/dist/validators/content-validator.js.map +0 -1
  72. package/dist/validators/description-validator.js +0 -150
  73. package/dist/validators/description-validator.js.map +0 -1
  74. package/dist/validators/file-structure-validator.js +0 -125
  75. package/dist/validators/file-structure-validator.js.map +0 -1
  76. package/dist/validators/frontmatter-validator.js +0 -190
  77. package/dist/validators/frontmatter-validator.js.map +0 -1
  78. package/dist/validators/references-validator.js +0 -155
  79. package/dist/validators/references-validator.js.map +0 -1
  80. package/dist/validators/text-analysis.js +0 -71
  81. package/dist/validators/text-analysis.js.map +0 -1
@@ -0,0 +1,721 @@
1
+ import { l as LIMITS } from "./output-Dz8fk6Gu.js";
2
+ import { a as validate_name_format, i as validate_hard_limits, r as validate_frontmatter_structure, t as extract_frontmatter } from "./frontmatter-validator-DO686mla.js";
3
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ //#region src/validators/text-analysis.ts
6
+ /**
7
+ * Count words in text
8
+ */
9
+ function count_words(text) {
10
+ return text.trim().split(/\s+/).filter((w) => w.length > 0).length;
11
+ }
12
+ /**
13
+ * Estimate tokens (rough approximation: 1 word ≈ 1.3 tokens for English)
14
+ */
15
+ function estimate_tokens(word_count) {
16
+ return Math.round(word_count * 1.3);
17
+ }
18
+ /**
19
+ * Estimate tokens for a string by counting words and applying ratio
20
+ */
21
+ function estimate_string_tokens(text) {
22
+ return estimate_tokens(count_words(text));
23
+ }
24
+ /**
25
+ * Remove HTML comments from content (for line counting)
26
+ */
27
+ function strip_html_comments(text) {
28
+ return text.replace(/<!--[\s\S]*?-->/g, "");
29
+ }
30
+ /**
31
+ * Extract keywords from text (simplified extraction)
32
+ */
33
+ function extract_keywords(text) {
34
+ const words = text.toLowerCase().replace(/[^\w\s-]/g, " ").split(/\s+/).filter((w) => w.length > 3);
35
+ return [...new Set(words)].filter((w) => ![
36
+ "this",
37
+ "that",
38
+ "with",
39
+ "from",
40
+ "have",
41
+ "will",
42
+ "when",
43
+ "what",
44
+ "where",
45
+ "which",
46
+ "their",
47
+ "them",
48
+ "then",
49
+ "than",
50
+ "these",
51
+ "those",
52
+ "there"
53
+ ].includes(w));
54
+ }
55
+ //#endregion
56
+ //#region src/validators/alignment-validator.ts
57
+ /**
58
+ * Alignment validation - checks description and content alignment
59
+ */
60
+ /**
61
+ * Analyze description and content alignment
62
+ */
63
+ function analyze_alignment(description, body) {
64
+ const desc_keywords = extract_keywords(description);
65
+ const content_keywords = extract_keywords(body);
66
+ const overlap = desc_keywords.filter((k) => content_keywords.includes(k));
67
+ const desc_only = desc_keywords.filter((k) => !content_keywords.includes(k));
68
+ const content_only = content_keywords.filter((k) => !desc_keywords.includes(k)).slice(0, 20);
69
+ const overlap_ratio = desc_keywords.length > 0 ? overlap.length / desc_keywords.length : 0;
70
+ let severity = "good";
71
+ let explanation = "Description aligns well with content";
72
+ if (overlap_ratio < .2 && desc_keywords.length > 5) {
73
+ severity = "critical";
74
+ explanation = `Very low keyword overlap (${Math.round(overlap_ratio * 100)}%). Description may not match skill content.`;
75
+ } else if (overlap_ratio < .3 && desc_keywords.length > 5) {
76
+ severity = "moderate";
77
+ explanation = `Low keyword overlap (${Math.round(overlap_ratio * 100)}%). Description may not accurately reflect skill content.`;
78
+ }
79
+ const keywords = {
80
+ description_keywords: desc_keywords,
81
+ content_keywords: content_keywords.slice(0, 30),
82
+ overlap,
83
+ description_only: desc_only,
84
+ content_only
85
+ };
86
+ const alignment = {
87
+ severity,
88
+ description_focus: desc_keywords.slice(0, 10),
89
+ content_focus: content_keywords.slice(0, 10),
90
+ matches: overlap,
91
+ mismatches: desc_only,
92
+ explanation
93
+ };
94
+ const warnings = [];
95
+ if (overlap_ratio < .3 && desc_keywords.length > 5) warnings.push({
96
+ type: "low_overlap",
97
+ message: `Low keyword overlap between description and content (${Math.round(overlap_ratio * 100)}%)\n → Description may not accurately reflect skill content`
98
+ });
99
+ return {
100
+ keywords,
101
+ alignment,
102
+ warnings
103
+ };
104
+ }
105
+ //#endregion
106
+ //#region src/validators/content-validator.ts
107
+ /**
108
+ * Content validation (Level 2 progressive disclosure)
109
+ */
110
+ /**
111
+ * Analyze content structure and patterns
112
+ */
113
+ function analyze_content_structure(body) {
114
+ const code_block_matches = body.match(/```[\s\S]*?```/g);
115
+ const code_blocks = code_block_matches ? code_block_matches.length : 0;
116
+ const heading_matches = body.match(/^#{1,6}\s/gm);
117
+ return {
118
+ code_blocks,
119
+ sections: heading_matches ? heading_matches.length : 0,
120
+ long_paragraphs: body.split(/\n\n+/).filter((p) => {
121
+ return count_words(p) > 100;
122
+ }).length
123
+ };
124
+ }
125
+ /**
126
+ * Validate progressive disclosure (word count, token budget, and line count)
127
+ */
128
+ function validate_content(body, options = {}) {
129
+ const { mode = "strict" } = options;
130
+ const limits = LIMITS[mode];
131
+ const word_count = count_words(body);
132
+ const estimated_tokens = estimate_tokens(word_count);
133
+ const line_count = strip_html_comments(body).trim().split("\n").length;
134
+ const structure = analyze_content_structure(body);
135
+ const validation = {
136
+ stats: {
137
+ word_count,
138
+ estimated_tokens,
139
+ line_count,
140
+ ...structure
141
+ },
142
+ warnings: [],
143
+ errors: []
144
+ };
145
+ if (word_count > limits.words.max) validation.errors.push({
146
+ type: "word_count",
147
+ message: `SKILL.md body has ${word_count} words (MAX: ${limits.words.max})\n → Move detailed content to references/ directory for Level 3 loading\n → This is a hard limit - skills must be concise`
148
+ });
149
+ else if (word_count > limits.words.good) validation.warnings.push({
150
+ type: "word_count",
151
+ message: `SKILL.md body has ${word_count} words (recommended: <${limits.words.good}, max: ${limits.words.max})\n → Consider moving examples/docs to references/ for better token efficiency`
152
+ });
153
+ if (line_count > limits.lines.max) validation.errors.push({
154
+ type: "line_count",
155
+ message: `SKILL.md body is ${line_count} lines (MAX: ${limits.lines.max})\n → Move detailed content to references/ directory\n → This is a hard limit - skills must be concise`
156
+ });
157
+ else if (line_count > limits.lines.good) validation.warnings.push({
158
+ type: "line_count",
159
+ message: `SKILL.md body is ${line_count} lines (recommended: <${limits.lines.good}, max: ${limits.lines.max})\n → Consider moving examples to references/ for Level 3 loading`
160
+ });
161
+ if (structure.code_blocks > 3) validation.warnings.push({
162
+ type: "code_blocks",
163
+ message: `SKILL.md contains ${structure.code_blocks} code examples (recommended: 1-2)\n → Move additional examples to references/examples.md for Level 3 loading`
164
+ });
165
+ if (structure.long_paragraphs > 3) validation.warnings.push({
166
+ type: "long_paragraphs",
167
+ message: `SKILL.md contains ${structure.long_paragraphs} lengthy paragraphs (>100 words)\n → Consider moving detailed explanations to references/`
168
+ });
169
+ if (structure.sections > 8) validation.warnings.push({
170
+ type: "sections",
171
+ message: `SKILL.md contains ${structure.sections} sections (recommended: 3-5)\n → Consider splitting into focused reference files`
172
+ });
173
+ if (!body.includes("## Quick Start") && !body.includes("## Quick start")) validation.warnings.push({
174
+ type: "missing_quick_start",
175
+ message: "Missing \"## Quick Start\" section\n → Add one minimal working example to help Claude get started quickly"
176
+ });
177
+ if (!body.includes("references/") && line_count > limits.lines.good) validation.warnings.push({
178
+ type: "no_references",
179
+ message: `No references/ links found but SKILL.md is ${line_count} lines\n → Consider splitting detailed content into reference files`
180
+ });
181
+ if (body.trim().length < 100) validation.warnings.push({
182
+ type: "short_body",
183
+ message: "SKILL.md body is very short"
184
+ });
185
+ if (body.includes("TODO") || body.includes("[Add your") || body.includes("[Provide")) validation.warnings.push({
186
+ type: "todo_placeholders",
187
+ message: "SKILL.md contains TODO placeholders"
188
+ });
189
+ return validation;
190
+ }
191
+ //#endregion
192
+ //#region src/validators/description-validator.ts
193
+ /**
194
+ * Description validation (Level 1 progressive disclosure)
195
+ */
196
+ /**
197
+ * Validate description length and quality
198
+ */
199
+ function validate_description_content(description) {
200
+ const desc_length = description.length;
201
+ const validation = {
202
+ stats: {
203
+ description_length: desc_length,
204
+ description_tokens: estimate_string_tokens(description)
205
+ },
206
+ warnings: [],
207
+ errors: []
208
+ };
209
+ if (desc_length > 250) validation.errors.push({
210
+ type: "length",
211
+ message: `Description is ${desc_length} characters (MAX: 250 — Claude truncates at this limit)\n → Keep descriptions concise - anything past 250 chars is never seen`
212
+ });
213
+ const lower_desc = description.toLowerCase();
214
+ if (!(lower_desc.includes("use when") || lower_desc.includes("use for") || lower_desc.includes("use to"))) validation.warnings.push({
215
+ type: "trigger",
216
+ message: "Description missing trigger keywords ('Use when...', 'Use for...', 'Use to...')\n → Help Claude know when to activate this skill"
217
+ });
218
+ const comma_count = (description.match(/,/g) || []).length;
219
+ if (desc_length > 150 && comma_count >= 5) validation.warnings.push({
220
+ type: "list_bloat",
221
+ message: `Description contains long lists (${comma_count} commas, ${desc_length} chars)\n → Move detailed lists to Level 2 (SKILL.md body) or Level 3 (references/)`
222
+ });
223
+ if (desc_length < 50) validation.warnings.push({
224
+ type: "short",
225
+ message: `Description is very short (${desc_length} chars, minimum recommended: 50)\n → Must answer both "what does it do" AND "when to use it"`
226
+ });
227
+ return validation;
228
+ }
229
+ /**
230
+ * Analyze trigger phrase in description
231
+ */
232
+ function analyze_trigger_phrase(description) {
233
+ const lower = description.toLowerCase();
234
+ const has_trigger = lower.includes("use when") || lower.includes("use for") || lower.includes("use to");
235
+ let trigger_phrase = null;
236
+ let trigger_type = "missing";
237
+ if (has_trigger) {
238
+ const match = description.match(/(use when|use for|use to)[^.!?]*/i);
239
+ if (match) {
240
+ trigger_phrase = match[0].trim();
241
+ trigger_type = trigger_phrase.length > 50 ? "specific" : "generic";
242
+ }
243
+ }
244
+ return {
245
+ has_explicit_trigger: has_trigger,
246
+ trigger_phrase,
247
+ trigger_type
248
+ };
249
+ }
250
+ /**
251
+ * Analyze user phrasing style
252
+ */
253
+ function analyze_user_phrasing(description) {
254
+ const issues = [];
255
+ const warnings = [];
256
+ const is_third_person = !/\b(I can|I will|I help|my|me)\b/i.test(description);
257
+ const first_person_patterns = /\b(I can|I will|I help|my|me)\b/i;
258
+ if (first_person_patterns.test(description)) {
259
+ const match = description.match(first_person_patterns);
260
+ if (match) warnings.push({
261
+ type: "first_person",
262
+ message: `Description uses first person: "${match[0]}"\n → Anthropic requires third-person voice (e.g., "Generates..." not "I can generate...")`
263
+ });
264
+ }
265
+ const second_person_patterns = /\b(You can|You should|You could|You'll|You will|You need|your)\b/i;
266
+ if (second_person_patterns.test(description)) {
267
+ const match = description.match(second_person_patterns);
268
+ if (match) warnings.push({
269
+ type: "second_person",
270
+ message: `Description uses second person: "${match[0]}"\n → Anthropic requires third-person voice (e.g., "Processes..." not "You can process...")`
271
+ });
272
+ }
273
+ const vague_patterns = /\b(helper|utility|tool|various|several|some)\b/i;
274
+ if (vague_patterns.test(description)) {
275
+ const match = description.match(vague_patterns);
276
+ if (match) warnings.push({
277
+ type: "vague",
278
+ message: `Description contains vague term: "${match[0]}"\n → Be specific about what the skill does`
279
+ });
280
+ }
281
+ const uses_gerund = /\b\w+ing\b/i.test(description);
282
+ const is_action_oriented = /^(create|build|design|analyze|test|validate|generate|process|manage|execute|handle|provide)/i.test(description.trim());
283
+ if (!uses_gerund && !is_action_oriented) warnings.push({
284
+ type: "passive",
285
+ message: "Description lacks action-oriented language\n → Start with a verb or gerund (e.g., \"Generates...\", \"Managing...\", \"Extract...\")"
286
+ });
287
+ return {
288
+ analysis: {
289
+ style_checks: {
290
+ is_third_person,
291
+ uses_gerund_form: uses_gerund,
292
+ is_action_oriented
293
+ },
294
+ issues
295
+ },
296
+ warnings
297
+ };
298
+ }
299
+ //#endregion
300
+ //#region src/validators/file-structure-validator.ts
301
+ /**
302
+ * File structure validation - paths, scripts, assets
303
+ */
304
+ /**
305
+ * Validate that skill directory exists and is valid
306
+ */
307
+ function validate_directory(skill_path) {
308
+ const errors = [];
309
+ if (!existsSync(skill_path)) {
310
+ errors.push({
311
+ type: "not_found",
312
+ message: `Skill directory does not exist: ${skill_path}`
313
+ });
314
+ return {
315
+ valid: false,
316
+ errors
317
+ };
318
+ }
319
+ if (!statSync(skill_path).isDirectory()) {
320
+ errors.push({
321
+ type: "not_directory",
322
+ message: `Path is not a directory: ${skill_path}`
323
+ });
324
+ return {
325
+ valid: false,
326
+ errors
327
+ };
328
+ }
329
+ return {
330
+ valid: true,
331
+ errors: []
332
+ };
333
+ }
334
+ /**
335
+ * Validate path formats (no Windows backslashes)
336
+ */
337
+ function validate_path_formats(content, file_name = "SKILL.md") {
338
+ const invalid_paths = [];
339
+ const errors = [];
340
+ content.split("\n").forEach((line, index) => {
341
+ if (line.trim().startsWith("```")) return;
342
+ const matches = line.match(/(?:scripts|references|assets|examples)\\[\w\\.-]+/g);
343
+ if (matches) matches.forEach((match) => {
344
+ const fixed = match.replace(/\\/g, "/");
345
+ invalid_paths.push({
346
+ line_number: index + 1,
347
+ path: match,
348
+ error: "Windows-style backslash detected",
349
+ suggested_fix: fixed
350
+ });
351
+ errors.push({
352
+ type: "windows_path",
353
+ message: `Windows-style path in ${file_name}:${index + 1}\n → Found: ${match}\n → Use: ${fixed}`
354
+ });
355
+ });
356
+ });
357
+ return {
358
+ validation: { invalid_paths },
359
+ errors
360
+ };
361
+ }
362
+ /**
363
+ * Validate scripts directory
364
+ */
365
+ function validate_scripts(skill_path) {
366
+ const scripts_dir = join(skill_path, "scripts");
367
+ const warnings = [];
368
+ if (existsSync(scripts_dir)) {
369
+ const script_files = readdirSync(scripts_dir).filter((f) => f.endsWith(".js") || f.endsWith(".ts") || f.endsWith(".mjs") || f.endsWith(".sh"));
370
+ if (script_files.length === 0) warnings.push({
371
+ type: "empty_directory",
372
+ message: "scripts/ directory exists but is empty"
373
+ });
374
+ for (const script_file of script_files) {
375
+ const script_path = join(scripts_dir, script_file);
376
+ if ((statSync(script_path).mode & 73) === 0) warnings.push({
377
+ type: "not_executable",
378
+ message: `Script is not executable: ${script_file}`
379
+ });
380
+ if (!readFileSync(script_path, "utf-8").split("\n")[0].startsWith("#!")) warnings.push({
381
+ type: "missing_shebang",
382
+ message: `Script missing shebang: ${script_file}`
383
+ });
384
+ }
385
+ }
386
+ return { warnings };
387
+ }
388
+ /**
389
+ * Validate assets directory
390
+ */
391
+ function validate_assets(skill_path) {
392
+ const assets_dir = join(skill_path, "assets");
393
+ const warnings = [];
394
+ if (existsSync(assets_dir)) {
395
+ if (readdirSync(assets_dir).length === 0) warnings.push({
396
+ type: "empty_directory",
397
+ message: "assets/ directory exists but is empty"
398
+ });
399
+ }
400
+ return { warnings };
401
+ }
402
+ //#endregion
403
+ //#region src/validators/references-validator.ts
404
+ /**
405
+ * References validation (Level 3 progressive disclosure)
406
+ */
407
+ /**
408
+ * Strip fenced code blocks from content to avoid parsing example links
409
+ */
410
+ function strip_code_blocks(content) {
411
+ return content.replace(/```[\s\S]*?```|~~~[\s\S]*?~~~/g, "");
412
+ }
413
+ /**
414
+ * Check nesting depth of reference files
415
+ */
416
+ function check_reference_nesting(skill_path, file_path, visited = /* @__PURE__ */ new Set()) {
417
+ if (visited.has(file_path)) return {
418
+ depth: 0,
419
+ references: []
420
+ };
421
+ visited.add(file_path);
422
+ const full_path = join(skill_path, file_path);
423
+ if (!existsSync(full_path)) return {
424
+ depth: 0,
425
+ references: []
426
+ };
427
+ const references = [...strip_code_blocks(readFileSync(full_path, "utf-8")).matchAll(/\[([^\]]+)\]\(([^)]+\.md)\)/g)].map((m) => m[2]);
428
+ if (references.length === 0) return {
429
+ depth: 1,
430
+ references: []
431
+ };
432
+ let max_depth = 1;
433
+ for (const ref of references) {
434
+ const nested = check_reference_nesting(skill_path, ref, new Set(visited));
435
+ max_depth = Math.max(max_depth, 1 + nested.depth);
436
+ }
437
+ return {
438
+ depth: max_depth,
439
+ references
440
+ };
441
+ }
442
+ /**
443
+ * Validate references directory and links
444
+ */
445
+ function validate_references(skill_path) {
446
+ const references_dir = join(skill_path, "references");
447
+ const skill_md_path = join(skill_path, "SKILL.md");
448
+ const files_found = [];
449
+ const files_referenced = [];
450
+ const missing_files = [];
451
+ const nesting_data = [];
452
+ const warnings = [];
453
+ const errors = [];
454
+ if (existsSync(references_dir)) {
455
+ const md_files = readdirSync(references_dir).filter((f) => f.endsWith(".md"));
456
+ files_found.push(...md_files.map((f) => `references/${f}`));
457
+ if (md_files.length === 0) warnings.push({
458
+ type: "empty_directory",
459
+ message: "references/ directory exists but is empty"
460
+ });
461
+ if (existsSync(skill_md_path)) {
462
+ const skill_content = readFileSync(skill_md_path, "utf-8");
463
+ for (const md_file of md_files) if (!skill_content.includes(md_file)) warnings.push({
464
+ type: "orphaned_file",
465
+ message: `Reference file 'references/${md_file}' not mentioned in SKILL.md`
466
+ });
467
+ }
468
+ }
469
+ if (existsSync(skill_path)) {
470
+ const root_md_files = readdirSync(skill_path).filter((f) => f.endsWith(".md") && f !== "SKILL.md" && f !== "README.md");
471
+ files_found.push(...root_md_files);
472
+ if (existsSync(skill_md_path)) {
473
+ const skill_content = readFileSync(skill_md_path, "utf-8");
474
+ for (const md_file of root_md_files) if (!skill_content.includes(md_file)) warnings.push({
475
+ type: "orphaned_file",
476
+ message: `Root file '${md_file}' not mentioned in SKILL.md`
477
+ });
478
+ }
479
+ }
480
+ if (existsSync(skill_md_path)) {
481
+ const matches = strip_code_blocks(readFileSync(skill_md_path, "utf-8")).matchAll(/\[([^\]]+)\]\(([^)]+\.md)\)/g);
482
+ for (const match of matches) {
483
+ const link_text = match[1];
484
+ const file_path = match[2];
485
+ const full_path = join(skill_path, file_path);
486
+ files_referenced.push(file_path);
487
+ if (!existsSync(full_path)) {
488
+ missing_files.push(file_path);
489
+ errors.push({
490
+ type: "missing_file",
491
+ message: `Referenced file not found: ${file_path}\n → Linked from: [${link_text}]\n → Create the file or remove the broken link`
492
+ });
493
+ } else {
494
+ const nesting = check_reference_nesting(skill_path, file_path);
495
+ let warning = null;
496
+ if (nesting.depth > 1) {
497
+ warning = `File has depth ${nesting.depth} (recommended: 1). Keep references one level deep from SKILL.md.`;
498
+ warnings.push({
499
+ type: "nesting_depth",
500
+ message: `${file_path} has nesting depth ${nesting.depth} (recommended: 1)\n → Keep references one level deep from SKILL.md for clarity`
501
+ });
502
+ }
503
+ nesting_data.push({
504
+ file: file_path,
505
+ references: nesting.references,
506
+ depth: nesting.depth,
507
+ warning
508
+ });
509
+ }
510
+ }
511
+ }
512
+ return {
513
+ validation: {
514
+ files_found,
515
+ files_referenced,
516
+ missing_files,
517
+ orphaned_files: files_found.filter((f) => !files_referenced.some((ref) => ref.includes(f))),
518
+ nesting: nesting_data,
519
+ max_nesting_depth: nesting_data.length > 0 ? Math.max(...nesting_data.map((n) => n.depth)) : 0
520
+ },
521
+ warnings,
522
+ errors
523
+ };
524
+ }
525
+ //#endregion
526
+ //#region src/core/validator.ts
527
+ var SkillValidator = class {
528
+ skill_path;
529
+ options;
530
+ errors = [];
531
+ warnings = [];
532
+ stats = {
533
+ word_count: 0,
534
+ estimated_tokens: 0,
535
+ line_count: 0,
536
+ description_length: 0,
537
+ description_tokens: 0,
538
+ code_blocks: 0,
539
+ sections: 0,
540
+ long_paragraphs: 0
541
+ };
542
+ structured_validation = {
543
+ hard_limits: {
544
+ name: {
545
+ length: 0,
546
+ limit: 64,
547
+ valid: true,
548
+ error: null
549
+ },
550
+ description: {
551
+ length: 0,
552
+ limit: 250,
553
+ valid: true,
554
+ error: null
555
+ }
556
+ },
557
+ name_format: {
558
+ name: "",
559
+ format_valid: true,
560
+ directory_name: "",
561
+ matches_directory: true,
562
+ errors: []
563
+ },
564
+ yaml_validation: {
565
+ valid: true,
566
+ has_frontmatter: false,
567
+ parse_error: null,
568
+ missing_fields: []
569
+ },
570
+ path_format: { invalid_paths: [] },
571
+ triggering: {
572
+ trigger_phrase: {
573
+ has_explicit_trigger: false,
574
+ trigger_phrase: null,
575
+ trigger_type: "missing"
576
+ },
577
+ user_phrasing: {
578
+ style_checks: {
579
+ is_third_person: true,
580
+ uses_gerund_form: true,
581
+ is_action_oriented: true
582
+ },
583
+ issues: []
584
+ },
585
+ keywords: {
586
+ description_keywords: [],
587
+ content_keywords: [],
588
+ overlap: [],
589
+ description_only: [],
590
+ content_only: []
591
+ },
592
+ alignment: {
593
+ severity: "good",
594
+ description_focus: [],
595
+ content_focus: [],
596
+ matches: [],
597
+ mismatches: [],
598
+ explanation: ""
599
+ }
600
+ }
601
+ };
602
+ constructor(skill_path, options = {}) {
603
+ this.skill_path = skill_path;
604
+ this.options = options;
605
+ }
606
+ error(msg) {
607
+ this.errors.push(`❌ ${msg}`);
608
+ }
609
+ warning(msg) {
610
+ this.warnings.push(`⚠️ ${msg}`);
611
+ }
612
+ validate_skill_md() {
613
+ const skill_md_path = join(this.skill_path, "SKILL.md");
614
+ if (!existsSync(skill_md_path)) {
615
+ this.error("SKILL.md file not found");
616
+ return false;
617
+ }
618
+ const content = readFileSync(skill_md_path, "utf-8");
619
+ const path_format_result = validate_path_formats(content);
620
+ this.structured_validation.path_format = path_format_result.validation;
621
+ path_format_result.errors.forEach((err) => this.error(err.message));
622
+ const frontmatter_validation = validate_frontmatter_structure(content);
623
+ this.structured_validation.yaml_validation = frontmatter_validation;
624
+ if (!frontmatter_validation.valid) {
625
+ if (frontmatter_validation.parse_error) this.error(frontmatter_validation.parse_error);
626
+ frontmatter_validation.missing_fields.forEach((field) => {
627
+ this.error(`SKILL.md frontmatter missing '${field}' field`);
628
+ });
629
+ return false;
630
+ }
631
+ if (frontmatter_validation.unknown_fields?.length) for (const field of frontmatter_validation.unknown_fields) this.warning(`Unknown frontmatter field '${field}'\n → See https://code.claude.com/docs/en/skills#frontmatter-reference`);
632
+ if (frontmatter_validation.field_value_warnings?.length) for (const warn of frontmatter_validation.field_value_warnings) this.warning(warn);
633
+ const { name, description, body, description_is_multiline } = extract_frontmatter(content);
634
+ if (!name || !description) {
635
+ this.error("Failed to extract name or description from frontmatter");
636
+ return false;
637
+ }
638
+ if (description_is_multiline) this.warning(`Multi-line description detected. Claude Code cannot recognize skills with multi-line descriptions.\n → Run 'claude-skills-cli doctor ${this.skill_path}' to fix automatically`);
639
+ const name_validation = validate_name_format(name, this.skill_path.replace(/\/+$/, "").split("/").pop() || "");
640
+ this.structured_validation.name_format = name_validation;
641
+ name_validation.errors.forEach((err) => this.error(err));
642
+ const hard_limits = validate_hard_limits(name, description);
643
+ this.structured_validation.hard_limits = hard_limits;
644
+ if (!hard_limits.name.valid && hard_limits.name.error) this.error(hard_limits.name.error);
645
+ if (!hard_limits.description.valid && hard_limits.description.error) this.error(hard_limits.description.error);
646
+ const desc_validation = validate_description_content(description);
647
+ this.stats.description_length = desc_validation.stats.description_length;
648
+ this.stats.description_tokens = desc_validation.stats.description_tokens;
649
+ desc_validation.errors.forEach((err) => this.error(err.message));
650
+ desc_validation.warnings.forEach((warn) => this.warning(warn.message));
651
+ const trigger_analysis = analyze_trigger_phrase(description);
652
+ this.structured_validation.triggering.trigger_phrase = trigger_analysis;
653
+ if (!trigger_analysis.has_explicit_trigger) this.warning("Description missing explicit trigger phrase ('Use when...', 'Use for...', 'Use to...')\n → Help Claude know when to activate this skill");
654
+ const { analysis: phrasing_analysis, warnings: phrasing_warnings } = analyze_user_phrasing(description);
655
+ this.structured_validation.triggering.user_phrasing = phrasing_analysis;
656
+ phrasing_warnings.forEach((warn) => this.warning(warn.message));
657
+ const alignment_result = analyze_alignment(description, body);
658
+ this.structured_validation.triggering.keywords = alignment_result.keywords;
659
+ this.structured_validation.triggering.alignment = alignment_result.alignment;
660
+ alignment_result.warnings.forEach((warn) => this.warning(warn.message));
661
+ const content_validation = validate_content(body, { mode: this.options.mode });
662
+ this.stats.word_count = content_validation.stats.word_count;
663
+ this.stats.estimated_tokens = content_validation.stats.estimated_tokens;
664
+ this.stats.line_count = content_validation.stats.line_count;
665
+ this.stats.code_blocks = content_validation.stats.code_blocks;
666
+ this.stats.sections = content_validation.stats.sections;
667
+ this.stats.long_paragraphs = content_validation.stats.long_paragraphs;
668
+ content_validation.errors.forEach((err) => this.error(err.message));
669
+ content_validation.warnings.forEach((warn) => this.warning(warn.message));
670
+ return true;
671
+ }
672
+ validate_all() {
673
+ const dir_result = validate_directory(this.skill_path);
674
+ if (!dir_result.valid) {
675
+ dir_result.errors.forEach((err) => this.error(err.message));
676
+ return {
677
+ errors: this.errors,
678
+ warnings: this.warnings,
679
+ is_valid: false,
680
+ stats: this.stats,
681
+ validation: this.structured_validation
682
+ };
683
+ }
684
+ this.validate_skill_md();
685
+ const refs_result = validate_references(this.skill_path);
686
+ refs_result.errors.forEach((err) => this.error(err.message));
687
+ refs_result.warnings.forEach((warn) => this.warning(warn.message));
688
+ validate_scripts(this.skill_path).warnings.forEach((warn) => this.warning(warn.message));
689
+ validate_assets(this.skill_path).warnings.forEach((warn) => this.warning(warn.message));
690
+ const mode_limits = LIMITS[this.options.mode || "strict"];
691
+ const line_limit = mode_limits.lines.max;
692
+ const word_limit = mode_limits.words.max;
693
+ this.structured_validation.progressive_disclosure = {
694
+ skill_md_size: {
695
+ lines: this.stats.line_count,
696
+ words: this.stats.word_count,
697
+ tokens: this.stats.estimated_tokens,
698
+ exceeds_line_limit: this.stats.line_count > line_limit,
699
+ exceeds_word_limit: this.stats.word_count > word_limit
700
+ },
701
+ references: refs_result.validation
702
+ };
703
+ return {
704
+ errors: this.errors,
705
+ warnings: this.warnings,
706
+ is_valid: this.errors.length === 0,
707
+ stats: this.stats,
708
+ validation: this.structured_validation
709
+ };
710
+ }
711
+ get_errors() {
712
+ return this.errors;
713
+ }
714
+ get_warnings() {
715
+ return this.warnings;
716
+ }
717
+ };
718
+ //#endregion
719
+ export { SkillValidator as t };
720
+
721
+ //# sourceMappingURL=validator-DV5zeeel.js.map