claude-skills-cli 0.0.20 → 0.0.22
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/dist/add-hook.cmd-JWw5UqA3.js +193 -0
- package/dist/add-hook.cmd-JWw5UqA3.js.map +1 -0
- package/dist/dependency-validator-CQJ1hCoU.js +382 -0
- package/dist/dependency-validator-CQJ1hCoU.js.map +1 -0
- package/dist/doctor.cmd-DJpHLDCV.js +142 -0
- package/dist/doctor.cmd-DJpHLDCV.js.map +1 -0
- package/dist/fs-CuGv3Ob2.js +23 -0
- package/dist/fs-CuGv3Ob2.js.map +1 -0
- package/dist/index.js +24 -22
- package/dist/index.js.map +1 -1
- package/dist/init.cmd-BdqImX8b.js +108 -0
- package/dist/init.cmd-BdqImX8b.js.map +1 -0
- package/dist/install.cmd-BaP8k9d2.js +79 -0
- package/dist/install.cmd-BaP8k9d2.js.map +1 -0
- package/dist/output-DiffPD2u.js +104 -0
- package/dist/output-DiffPD2u.js.map +1 -0
- package/dist/package.cmd-BYhkheya.js +107 -0
- package/dist/package.cmd-BYhkheya.js.map +1 -0
- package/dist/stats.cmd-Dd46qjoV.js +154 -0
- package/dist/stats.cmd-Dd46qjoV.js.map +1 -0
- package/dist/{core/templates.js → templates-fyteNbD0.js} +20 -13
- package/dist/templates-fyteNbD0.js.map +1 -0
- package/dist/validate.cmd-BxF4HNsu.js +96 -0
- package/dist/validate.cmd-BxF4HNsu.js.map +1 -0
- package/dist/validator-Dp5x-OjP.js +729 -0
- package/dist/validator-Dp5x-OjP.js.map +1 -0
- package/package.json +34 -35
- package/dist/commands/add-hook.cmd.js +0 -35
- package/dist/commands/add-hook.cmd.js.map +0 -1
- package/dist/commands/add-hook.js +0 -216
- package/dist/commands/add-hook.js.map +0 -1
- package/dist/commands/doctor.cmd.js +0 -19
- package/dist/commands/doctor.cmd.js.map +0 -1
- package/dist/commands/doctor.js +0 -128
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/init.cmd.js +0 -37
- package/dist/commands/init.cmd.js.map +0 -1
- package/dist/commands/init.js +0 -86
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/install.cmd.js +0 -23
- package/dist/commands/install.cmd.js.map +0 -1
- package/dist/commands/install.js +0 -64
- package/dist/commands/install.js.map +0 -1
- package/dist/commands/package.cmd.js +0 -28
- package/dist/commands/package.cmd.js.map +0 -1
- package/dist/commands/package.js +0 -134
- package/dist/commands/package.js.map +0 -1
- package/dist/commands/stats.cmd.js +0 -19
- package/dist/commands/stats.cmd.js.map +0 -1
- package/dist/commands/stats.js +0 -154
- package/dist/commands/stats.js.map +0 -1
- package/dist/commands/validate.cmd.js +0 -39
- package/dist/commands/validate.cmd.js.map +0 -1
- package/dist/commands/validate.js +0 -77
- package/dist/commands/validate.js.map +0 -1
- package/dist/core/templates.js.map +0 -1
- package/dist/core/validator.js +0 -252
- package/dist/core/validator.js.map +0 -1
- package/dist/help.js +0 -305
- package/dist/help.js.map +0 -1
- package/dist/skills/.gitkeep +0 -0
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/utils/fs.js +0 -25
- package/dist/utils/fs.js.map +0 -1
- package/dist/utils/output.js +0 -102
- package/dist/utils/output.js.map +0 -1
- package/dist/validators/alignment-validator.js +0 -54
- package/dist/validators/alignment-validator.js.map +0 -1
- package/dist/validators/content-validator.js +0 -156
- package/dist/validators/content-validator.js.map +0 -1
- package/dist/validators/description-validator.js +0 -150
- package/dist/validators/description-validator.js.map +0 -1
- package/dist/validators/file-structure-validator.js +0 -125
- package/dist/validators/file-structure-validator.js.map +0 -1
- package/dist/validators/frontmatter-validator.js +0 -190
- package/dist/validators/frontmatter-validator.js.map +0 -1
- package/dist/validators/references-validator.js +0 -155
- package/dist/validators/references-validator.js.map +0 -1
- package/dist/validators/text-analysis.js +0 -71
- package/dist/validators/text-analysis.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator-Dp5x-OjP.js","names":[],"sources":["../src/validators/text-analysis.ts","../src/validators/alignment-validator.ts","../src/validators/content-validator.ts","../src/validators/description-validator.ts","../src/validators/file-structure-validator.ts","../src/validators/references-validator.ts","../src/core/validator.ts"],"sourcesContent":["/**\n * Text analysis utilities for skill validation\n */\n\n/**\n * Extract body content from SKILL.md (excluding YAML frontmatter)\n */\nexport function extract_body(content: string): string {\n\tconst parts = content.split('---\\n');\n\treturn parts.length >= 3\n\t\t? parts.slice(2).join('---\\n').trim()\n\t\t: content;\n}\n\n/**\n * Count words in text\n */\nexport function count_words(text: string): number {\n\treturn text\n\t\t.trim()\n\t\t.split(/\\s+/)\n\t\t.filter((w) => w.length > 0).length;\n}\n\n/**\n * Estimate tokens (rough approximation: 1 word ≈ 1.3 tokens for English)\n */\nexport function estimate_tokens(word_count: number): number {\n\treturn Math.round(word_count * 1.3);\n}\n\n/**\n * Estimate tokens for a string by counting words and applying ratio\n */\nexport function estimate_string_tokens(text: string): number {\n\tconst word_count = count_words(text);\n\treturn estimate_tokens(word_count);\n}\n\n/**\n * Remove HTML comments from content (for line counting)\n */\nexport function strip_html_comments(text: string): string {\n\treturn text.replace(/<!--[\\s\\S]*?-->/g, '');\n}\n\n/**\n * Extract keywords from text (simplified extraction)\n */\nexport function extract_keywords(text: string): string[] {\n\tconst words = text\n\t\t.toLowerCase()\n\t\t.replace(/[^\\w\\s-]/g, ' ')\n\t\t.split(/\\s+/)\n\t\t.filter((w) => w.length > 3);\n\n\tconst unique = [...new Set(words)];\n\treturn unique.filter(\n\t\t(w) =>\n\t\t\t![\n\t\t\t\t'this',\n\t\t\t\t'that',\n\t\t\t\t'with',\n\t\t\t\t'from',\n\t\t\t\t'have',\n\t\t\t\t'will',\n\t\t\t\t'when',\n\t\t\t\t'what',\n\t\t\t\t'where',\n\t\t\t\t'which',\n\t\t\t\t'their',\n\t\t\t\t'them',\n\t\t\t\t'then',\n\t\t\t\t'than',\n\t\t\t\t'these',\n\t\t\t\t'those',\n\t\t\t\t'there',\n\t\t\t].includes(w),\n\t);\n}\n","/**\n * Alignment validation - checks description and content alignment\n */\n\nimport { extract_keywords } from './text-analysis.js';\nimport type { KeywordAnalysis, AlignmentAnalysis } from '../types.js';\n\nexport interface AlignmentWarning {\n\ttype: 'low_overlap';\n\tmessage: string;\n}\n\nexport interface AlignmentValidation {\n\tkeywords: KeywordAnalysis;\n\talignment: AlignmentAnalysis;\n\twarnings: AlignmentWarning[];\n}\n\n/**\n * Analyze description and content alignment\n */\nexport function analyze_alignment(\n\tdescription: string,\n\tbody: string,\n): AlignmentValidation {\n\tconst desc_keywords = extract_keywords(description);\n\tconst content_keywords = extract_keywords(body);\n\n\tconst overlap = desc_keywords.filter((k) =>\n\t\tcontent_keywords.includes(k),\n\t);\n\tconst desc_only = desc_keywords.filter(\n\t\t(k) => !content_keywords.includes(k),\n\t);\n\tconst content_only = content_keywords\n\t\t.filter((k) => !desc_keywords.includes(k))\n\t\t.slice(0, 20);\n\n\tconst overlap_ratio =\n\t\tdesc_keywords.length > 0\n\t\t\t? overlap.length / desc_keywords.length\n\t\t\t: 0;\n\n\tlet severity: 'good' | 'moderate' | 'critical' = 'good';\n\tlet explanation = 'Description aligns well with content';\n\n\tif (overlap_ratio < 0.2 && desc_keywords.length > 5) {\n\t\tseverity = 'critical';\n\t\texplanation = `Very low keyword overlap (${Math.round(overlap_ratio * 100)}%). Description may not match skill content.`;\n\t} else if (overlap_ratio < 0.3 && desc_keywords.length > 5) {\n\t\tseverity = 'moderate';\n\t\texplanation = `Low keyword overlap (${Math.round(overlap_ratio * 100)}%). Description may not accurately reflect skill content.`;\n\t}\n\n\tconst keywords: KeywordAnalysis = {\n\t\tdescription_keywords: desc_keywords,\n\t\tcontent_keywords: content_keywords.slice(0, 30),\n\t\toverlap,\n\t\tdescription_only: desc_only,\n\t\tcontent_only,\n\t};\n\n\tconst alignment: AlignmentAnalysis = {\n\t\tseverity,\n\t\tdescription_focus: desc_keywords.slice(0, 10),\n\t\tcontent_focus: content_keywords.slice(0, 10),\n\t\tmatches: overlap,\n\t\tmismatches: desc_only,\n\t\texplanation,\n\t};\n\n\tconst warnings: AlignmentWarning[] = [];\n\n\tif (overlap_ratio < 0.3 && desc_keywords.length > 5) {\n\t\twarnings.push({\n\t\t\ttype: 'low_overlap',\n\t\t\tmessage:\n\t\t\t\t`Low keyword overlap between description and content (${Math.round(overlap_ratio * 100)}%)\\n` +\n\t\t\t\t` → Description may not accurately reflect skill content`,\n\t\t});\n\t}\n\n\treturn { keywords, alignment, warnings };\n}\n","/**\n * Content validation (Level 2 progressive disclosure)\n */\n\nimport {\n\tLIMITS,\n\tLONG_PARAGRAPH_WORDS,\n\tMIN_BODY_LENGTH,\n} from '../constants.js';\nimport {\n\tcount_words,\n\testimate_tokens,\n\tstrip_html_comments,\n} from './text-analysis.js';\n\nexport interface ContentStats {\n\tword_count: number;\n\testimated_tokens: number;\n\tline_count: number;\n\tcode_blocks: number;\n\tsections: number;\n\tlong_paragraphs: number;\n}\n\nexport interface ContentWarning {\n\ttype:\n\t\t| 'word_count'\n\t\t| 'line_count'\n\t\t| 'code_blocks'\n\t\t| 'long_paragraphs'\n\t\t| 'sections'\n\t\t| 'missing_quick_start'\n\t\t| 'no_references'\n\t\t| 'short_body'\n\t\t| 'todo_placeholders';\n\tmessage: string;\n}\n\nexport interface ContentError {\n\ttype: 'word_count' | 'line_count';\n\tmessage: string;\n}\n\nexport interface ContentValidation {\n\tstats: ContentStats;\n\twarnings: ContentWarning[];\n\terrors: ContentError[];\n}\n\nimport type { ValidationMode } from '../types.js';\n\nexport interface ContentValidationOptions {\n\tmode?: ValidationMode;\n}\n\n/**\n * Analyze content structure and patterns\n */\nexport function analyze_content_structure(\n\tbody: string,\n): Pick<\n\tContentStats,\n\t'code_blocks' | 'sections' | 'long_paragraphs'\n> {\n\t// Count code blocks\n\tconst code_block_matches = body.match(/```[\\s\\S]*?```/g);\n\tconst code_blocks = code_block_matches\n\t\t? code_block_matches.length\n\t\t: 0;\n\n\t// Count markdown sections (headings)\n\tconst heading_matches = body.match(/^#{1,6}\\s/gm);\n\tconst sections = heading_matches ? heading_matches.length : 0;\n\n\t// Count long paragraphs\n\tconst paragraphs = body.split(/\\n\\n+/);\n\tconst long_paragraphs = paragraphs.filter((p) => {\n\t\tconst words = count_words(p);\n\t\treturn words > LONG_PARAGRAPH_WORDS;\n\t}).length;\n\n\treturn { code_blocks, sections, long_paragraphs };\n}\n\n/**\n * Validate progressive disclosure (word count, token budget, and line count)\n */\nexport function validate_content(\n\tbody: string,\n\toptions: ContentValidationOptions = {},\n): ContentValidation {\n\tconst { mode = 'strict' } = options;\n\tconst limits = LIMITS[mode];\n\n\tconst word_count = count_words(body);\n\tconst estimated_tokens = estimate_tokens(word_count);\n\n\t// Strip HTML comments before counting lines (progressive disclosure guidance shouldn't inflate count)\n\tconst body_without_comments = strip_html_comments(body);\n\tconst line_count = body_without_comments.trim().split('\\n').length;\n\n\t// Analyze content structure\n\tconst structure = analyze_content_structure(body);\n\n\tconst validation: ContentValidation = {\n\t\tstats: {\n\t\t\tword_count,\n\t\t\testimated_tokens,\n\t\t\tline_count,\n\t\t\t...structure,\n\t\t},\n\t\twarnings: [],\n\t\terrors: [],\n\t};\n\n\t// Word count validation\n\tif (word_count > limits.words.max) {\n\t\tvalidation.errors.push({\n\t\t\ttype: 'word_count',\n\t\t\tmessage:\n\t\t\t\t`SKILL.md body has ${word_count} words (MAX: ${limits.words.max})\\n` +\n\t\t\t\t` → Move detailed content to references/ directory for Level 3 loading\\n` +\n\t\t\t\t` → This is a hard limit - skills must be concise`,\n\t\t});\n\t} else if (word_count > limits.words.good) {\n\t\tvalidation.warnings.push({\n\t\t\ttype: 'word_count',\n\t\t\tmessage:\n\t\t\t\t`SKILL.md body has ${word_count} words (recommended: <${limits.words.good}, max: ${limits.words.max})\\n` +\n\t\t\t\t` → Consider moving examples/docs to references/ for better token efficiency`,\n\t\t});\n\t}\n\n\t// Line count validation (Level 2 progressive disclosure)\n\tif (line_count > limits.lines.max) {\n\t\tvalidation.errors.push({\n\t\t\ttype: 'line_count',\n\t\t\tmessage:\n\t\t\t\t`SKILL.md body is ${line_count} lines (MAX: ${limits.lines.max})\\n` +\n\t\t\t\t` → Move detailed content to references/ directory\\n` +\n\t\t\t\t` → This is a hard limit - skills must be concise`,\n\t\t});\n\t} else if (line_count > limits.lines.good) {\n\t\tvalidation.warnings.push({\n\t\t\ttype: 'line_count',\n\t\t\tmessage:\n\t\t\t\t`SKILL.md body is ${line_count} lines (recommended: <${limits.lines.good}, max: ${limits.lines.max})\\n` +\n\t\t\t\t` → Consider moving examples to references/ for Level 3 loading`,\n\t\t});\n\t}\n\n\t// Content analysis warnings\n\t// Code blocks: Recommend 1-2, warn at >3\n\tif (structure.code_blocks > 3) {\n\t\tvalidation.warnings.push({\n\t\t\ttype: 'code_blocks',\n\t\t\tmessage:\n\t\t\t\t`SKILL.md contains ${structure.code_blocks} code examples (recommended: 1-2)\\n` +\n\t\t\t\t` → Move additional examples to references/examples.md for Level 3 loading`,\n\t\t});\n\t}\n\n\t// Long paragraphs\n\tif (structure.long_paragraphs > 3) {\n\t\tvalidation.warnings.push({\n\t\t\ttype: 'long_paragraphs',\n\t\t\tmessage:\n\t\t\t\t`SKILL.md contains ${structure.long_paragraphs} lengthy paragraphs (>${LONG_PARAGRAPH_WORDS} words)\\n` +\n\t\t\t\t` → Consider moving detailed explanations to references/`,\n\t\t});\n\t}\n\n\t// Sections: Recommend 3-5, warn at >8\n\tif (structure.sections > 8) {\n\t\tvalidation.warnings.push({\n\t\t\ttype: 'sections',\n\t\t\tmessage:\n\t\t\t\t`SKILL.md contains ${structure.sections} sections (recommended: 3-5)\\n` +\n\t\t\t\t` → Consider splitting into focused reference files`,\n\t\t});\n\t}\n\n\t// Check for \"Quick Start\" section\n\tif (\n\t\t!body.includes('## Quick Start') &&\n\t\t!body.includes('## Quick start')\n\t) {\n\t\tvalidation.warnings.push({\n\t\t\ttype: 'missing_quick_start',\n\t\t\tmessage:\n\t\t\t\t`Missing \"## Quick Start\" section\\n` +\n\t\t\t\t` → Add one minimal working example to help Claude get started quickly`,\n\t\t});\n\t}\n\n\t// Check for references/ links when body is long (warn when exceeding good threshold)\n\tconst has_references = body.includes('references/');\n\tif (!has_references && line_count > limits.lines.good) {\n\t\tvalidation.warnings.push({\n\t\t\ttype: 'no_references',\n\t\t\tmessage:\n\t\t\t\t`No references/ links found but SKILL.md is ${line_count} lines\\n` +\n\t\t\t\t` → Consider splitting detailed content into reference files`,\n\t\t});\n\t}\n\n\t// Check body content\n\tif (body.trim().length < MIN_BODY_LENGTH) {\n\t\tvalidation.warnings.push({\n\t\t\ttype: 'short_body',\n\t\t\tmessage: 'SKILL.md body is very short',\n\t\t});\n\t}\n\n\t// Check for TODO placeholders\n\tif (\n\t\tbody.includes('TODO') ||\n\t\tbody.includes('[Add your') ||\n\t\tbody.includes('[Provide')\n\t) {\n\t\tvalidation.warnings.push({\n\t\t\ttype: 'todo_placeholders',\n\t\t\tmessage: 'SKILL.md contains TODO placeholders',\n\t\t});\n\t}\n\n\treturn validation;\n}\n","/**\n * Description validation (Level 1 progressive disclosure)\n */\n\nimport {\n\tDESCRIPTION_MAX_LENGTH,\n\tDESCRIPTION_MIN_LENGTH,\n} from '../constants.js';\nimport type {\n\tTriggerPhraseAnalysis,\n\tUserPhrasingAnalysis,\n} from '../types.js';\nimport { estimate_string_tokens } from './text-analysis.js';\n\nexport interface DescriptionStats {\n\tdescription_length: number;\n\tdescription_tokens: number;\n}\n\nexport interface DescriptionWarning {\n\ttype:\n\t\t| 'length'\n\t\t| 'trigger'\n\t\t| 'list_bloat'\n\t\t| 'short'\n\t\t| 'first_person'\n\t\t| 'second_person'\n\t\t| 'vague'\n\t\t| 'passive';\n\tmessage: string;\n}\n\nexport interface DescriptionError {\n\ttype: 'length';\n\tmessage: string;\n}\n\nexport interface DescriptionValidation {\n\tstats: DescriptionStats;\n\twarnings: DescriptionWarning[];\n\terrors: DescriptionError[];\n}\n\n/**\n * Validate description length and quality\n */\nexport function validate_description_content(\n\tdescription: string,\n): DescriptionValidation {\n\tconst desc_length = description.length;\n\tconst desc_tokens = estimate_string_tokens(description);\n\n\tconst validation: DescriptionValidation = {\n\t\tstats: {\n\t\t\tdescription_length: desc_length,\n\t\t\tdescription_tokens: desc_tokens,\n\t\t},\n\t\twarnings: [],\n\t\terrors: [],\n\t};\n\n\t// Enforced limit: Claude truncates descriptions at this limit in skill listing\n\tif (desc_length > DESCRIPTION_MAX_LENGTH) {\n\t\tvalidation.errors.push({\n\t\t\ttype: 'length',\n\t\t\tmessage:\n\t\t\t\t`Description is ${desc_length} characters (MAX: ${DESCRIPTION_MAX_LENGTH} — Claude truncates at this limit)\\n` +\n\t\t\t\t` → Keep descriptions concise - anything past ${DESCRIPTION_MAX_LENGTH} chars is never seen`,\n\t\t});\n\t}\n\n\t// Check for trigger keywords\n\tconst lower_desc = description.toLowerCase();\n\tconst has_trigger =\n\t\tlower_desc.includes('use when') ||\n\t\tlower_desc.includes('use for') ||\n\t\tlower_desc.includes('use to');\n\n\tif (!has_trigger) {\n\t\tvalidation.warnings.push({\n\t\t\ttype: 'trigger',\n\t\t\tmessage:\n\t\t\t\t`Description missing trigger keywords ('Use when...', 'Use for...', 'Use to...')\\n` +\n\t\t\t\t` → Help Claude know when to activate this skill`,\n\t\t});\n\t}\n\n\t// Check for list bloat (multiple commas indicating detailed lists)\n\t// Only warn if BOTH long description AND many commas (allows concise technical lists)\n\tconst comma_count = (description.match(/,/g) || []).length;\n\tif (desc_length > 150 && comma_count >= 5) {\n\t\tvalidation.warnings.push({\n\t\t\ttype: 'list_bloat',\n\t\t\tmessage:\n\t\t\t\t`Description contains long lists (${comma_count} commas, ${desc_length} chars)\\n` +\n\t\t\t\t` → Move detailed lists to Level 2 (SKILL.md body) or Level 3 (references/)`,\n\t\t});\n\t}\n\n\t// Short description check\n\tif (desc_length < DESCRIPTION_MIN_LENGTH) {\n\t\tvalidation.warnings.push({\n\t\t\ttype: 'short',\n\t\t\tmessage:\n\t\t\t\t`Description is very short (${desc_length} chars, minimum recommended: ${DESCRIPTION_MIN_LENGTH})\\n` +\n\t\t\t\t` → Must answer both \"what does it do\" AND \"when to use it\"`,\n\t\t});\n\t}\n\n\treturn validation;\n}\n\n/**\n * Analyze trigger phrase in description\n */\nexport function analyze_trigger_phrase(\n\tdescription: string,\n): TriggerPhraseAnalysis {\n\tconst lower = description.toLowerCase();\n\tconst has_trigger =\n\t\tlower.includes('use when') ||\n\t\tlower.includes('use for') ||\n\t\tlower.includes('use to');\n\n\tlet trigger_phrase: string | null = null;\n\tlet trigger_type: 'specific' | 'generic' | 'missing' = 'missing';\n\n\tif (has_trigger) {\n\t\tconst match = description.match(\n\t\t\t/(use when|use for|use to)[^.!?]*/i,\n\t\t);\n\t\tif (match) {\n\t\t\ttrigger_phrase = match[0].trim();\n\t\t\ttrigger_type =\n\t\t\t\ttrigger_phrase.length > 50 ? 'specific' : 'generic';\n\t\t}\n\t}\n\n\treturn {\n\t\thas_explicit_trigger: has_trigger,\n\t\ttrigger_phrase,\n\t\ttrigger_type,\n\t};\n}\n\n/**\n * Analyze user phrasing style\n */\nexport function analyze_user_phrasing(description: string): {\n\tanalysis: UserPhrasingAnalysis;\n\twarnings: DescriptionWarning[];\n} {\n\tconst issues: Array<{\n\t\ttype: 'first_person' | 'passive_voice' | 'vague';\n\t\ttext: string;\n\t\tsuggestion: string;\n\t}> = [];\n\tconst warnings: DescriptionWarning[] = [];\n\n\t// Check for first person\n\tconst is_third_person = !/\\b(I can|I will|I help|my|me)\\b/i.test(\n\t\tdescription,\n\t);\n\tconst first_person_patterns = /\\b(I can|I will|I help|my|me)\\b/i;\n\tif (first_person_patterns.test(description)) {\n\t\tconst match = description.match(first_person_patterns);\n\t\tif (match) {\n\t\t\twarnings.push({\n\t\t\t\ttype: 'first_person',\n\t\t\t\tmessage:\n\t\t\t\t\t`Description uses first person: \"${match[0]}\"\\n` +\n\t\t\t\t\t` → Anthropic requires third-person voice (e.g., \"Generates...\" not \"I can generate...\")`,\n\t\t\t});\n\t\t}\n\t}\n\n\t// Check for second person\n\tconst second_person_patterns =\n\t\t/\\b(You can|You should|You could|You'll|You will|You need|your)\\b/i;\n\tif (second_person_patterns.test(description)) {\n\t\tconst match = description.match(second_person_patterns);\n\t\tif (match) {\n\t\t\twarnings.push({\n\t\t\t\ttype: 'second_person',\n\t\t\t\tmessage:\n\t\t\t\t\t`Description uses second person: \"${match[0]}\"\\n` +\n\t\t\t\t\t` → Anthropic requires third-person voice (e.g., \"Processes...\" not \"You can process...\")`,\n\t\t\t});\n\t\t}\n\t}\n\n\t// Check for vague terms\n\tconst vague_patterns =\n\t\t/\\b(helper|utility|tool|various|several|some)\\b/i;\n\tif (vague_patterns.test(description)) {\n\t\tconst match = description.match(vague_patterns);\n\t\tif (match) {\n\t\t\twarnings.push({\n\t\t\t\ttype: 'vague',\n\t\t\t\tmessage:\n\t\t\t\t\t`Description contains vague term: \"${match[0]}\"\\n` +\n\t\t\t\t\t` → Be specific about what the skill does`,\n\t\t\t});\n\t\t}\n\t}\n\n\t// Check for gerund form (verbs ending in -ing)\n\tconst uses_gerund = /\\b\\w+ing\\b/i.test(description);\n\n\t// Check for action-oriented (starts with action verbs)\n\tconst action_verbs =\n\t\t/^(create|build|design|analyze|test|validate|generate|process|manage|execute|handle|provide)/i;\n\tconst is_action_oriented = action_verbs.test(description.trim());\n\n\t// Suggest action-oriented language if neither gerund nor action verb\n\tif (!uses_gerund && !is_action_oriented) {\n\t\twarnings.push({\n\t\t\ttype: 'passive',\n\t\t\tmessage:\n\t\t\t\t`Description lacks action-oriented language\\n` +\n\t\t\t\t` → Start with a verb or gerund (e.g., \"Generates...\", \"Managing...\", \"Extract...\")`,\n\t\t});\n\t}\n\n\tconst analysis: UserPhrasingAnalysis = {\n\t\tstyle_checks: {\n\t\t\tis_third_person,\n\t\t\tuses_gerund_form: uses_gerund,\n\t\t\tis_action_oriented,\n\t\t},\n\t\tissues,\n\t};\n\n\treturn { analysis, warnings };\n}\n","/**\n * File structure validation - paths, scripts, assets\n */\n\nimport {\n\texistsSync,\n\treaddirSync,\n\treadFileSync,\n\tstatSync,\n} from 'node:fs';\nimport { join } from 'node:path';\nimport type {\n\tPathFormatValidation,\n\tPathFormatIssue,\n} from '../types.js';\n\nexport interface PathFormatError {\n\ttype: 'windows_path';\n\tmessage: string;\n}\n\nexport interface ScriptsWarning {\n\ttype: 'empty_directory' | 'not_executable' | 'missing_shebang';\n\tmessage: string;\n}\n\nexport interface AssetsWarning {\n\ttype: 'empty_directory';\n\tmessage: string;\n}\n\nexport interface DirectoryError {\n\ttype: 'not_found' | 'not_directory';\n\tmessage: string;\n}\n\n/**\n * Validate that skill directory exists and is valid\n */\nexport function validate_directory(skill_path: string): {\n\tvalid: boolean;\n\terrors: DirectoryError[];\n} {\n\tconst errors: DirectoryError[] = [];\n\n\tif (!existsSync(skill_path)) {\n\t\terrors.push({\n\t\t\ttype: 'not_found',\n\t\t\tmessage: `Skill directory does not exist: ${skill_path}`,\n\t\t});\n\t\treturn { valid: false, errors };\n\t}\n\n\tconst stats = statSync(skill_path);\n\tif (!stats.isDirectory()) {\n\t\terrors.push({\n\t\t\ttype: 'not_directory',\n\t\t\tmessage: `Path is not a directory: ${skill_path}`,\n\t\t});\n\t\treturn { valid: false, errors };\n\t}\n\n\treturn { valid: true, errors: [] };\n}\n\n/**\n * Validate path formats (no Windows backslashes)\n */\nexport function validate_path_formats(\n\tcontent: string,\n\tfile_name: string = 'SKILL.md',\n): {\n\tvalidation: PathFormatValidation;\n\terrors: PathFormatError[];\n} {\n\tconst invalid_paths: PathFormatIssue[] = [];\n\tconst errors: PathFormatError[] = [];\n\tconst lines = content.split('\\n');\n\n\tlines.forEach((line, index) => {\n\t\t// Skip code blocks (they might legitimately show Windows paths as examples)\n\t\tif (line.trim().startsWith('```')) return;\n\n\t\t// Detect backslashes in file paths\n\t\t// Match patterns like: scripts\\file.py, references\\doc.md, etc.\n\t\tconst backslash_pattern =\n\t\t\t/(?:scripts|references|assets|examples)\\\\[\\w\\\\.-]+/g;\n\t\tconst matches = line.match(backslash_pattern);\n\n\t\tif (matches) {\n\t\t\tmatches.forEach((match) => {\n\t\t\t\tconst fixed = match.replace(/\\\\/g, '/');\n\n\t\t\t\t// Store in validation\n\t\t\t\tinvalid_paths.push({\n\t\t\t\t\tline_number: index + 1,\n\t\t\t\t\tpath: match,\n\t\t\t\t\terror: 'Windows-style backslash detected',\n\t\t\t\t\tsuggested_fix: fixed,\n\t\t\t\t});\n\n\t\t\t\terrors.push({\n\t\t\t\t\ttype: 'windows_path',\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t`Windows-style path in ${file_name}:${index + 1}\\n` +\n\t\t\t\t\t\t` → Found: ${match}\\n` +\n\t\t\t\t\t\t` → Use: ${fixed}`,\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t});\n\n\treturn {\n\t\tvalidation: { invalid_paths },\n\t\terrors,\n\t};\n}\n\n/**\n * Validate scripts directory\n */\nexport function validate_scripts(skill_path: string): {\n\twarnings: ScriptsWarning[];\n} {\n\tconst scripts_dir = join(skill_path, 'scripts');\n\tconst warnings: ScriptsWarning[] = [];\n\n\tif (existsSync(scripts_dir)) {\n\t\tconst files = readdirSync(scripts_dir);\n\t\tconst script_files = files.filter(\n\t\t\t(f) =>\n\t\t\t\tf.endsWith('.js') ||\n\t\t\t\tf.endsWith('.ts') ||\n\t\t\t\tf.endsWith('.mjs') ||\n\t\t\t\tf.endsWith('.sh'),\n\t\t);\n\n\t\tif (script_files.length === 0) {\n\t\t\twarnings.push({\n\t\t\t\ttype: 'empty_directory',\n\t\t\t\tmessage: 'scripts/ directory exists but is empty',\n\t\t\t});\n\t\t}\n\n\t\tfor (const script_file of script_files) {\n\t\t\tconst script_path = join(scripts_dir, script_file);\n\t\t\tconst stats = statSync(script_path);\n\n\t\t\t// Check if executable (0o111 = --x--x--x)\n\t\t\tif ((stats.mode & 0o111) === 0) {\n\t\t\t\twarnings.push({\n\t\t\t\t\ttype: 'not_executable',\n\t\t\t\t\tmessage: `Script is not executable: ${script_file}`,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Check for shebang\n\t\t\tconst content = readFileSync(script_path, 'utf-8');\n\t\t\tconst first_line = content.split('\\n')[0];\n\t\t\tif (!first_line.startsWith('#!')) {\n\t\t\t\twarnings.push({\n\t\t\t\t\ttype: 'missing_shebang',\n\t\t\t\t\tmessage: `Script missing shebang: ${script_file}`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { warnings };\n}\n\n/**\n * Validate assets directory\n */\nexport function validate_assets(skill_path: string): {\n\twarnings: AssetsWarning[];\n} {\n\tconst assets_dir = join(skill_path, 'assets');\n\tconst warnings: AssetsWarning[] = [];\n\n\tif (existsSync(assets_dir)) {\n\t\tconst files = readdirSync(assets_dir);\n\n\t\tif (files.length === 0) {\n\t\t\twarnings.push({\n\t\t\t\ttype: 'empty_directory',\n\t\t\t\tmessage: 'assets/ directory exists but is empty',\n\t\t\t});\n\t\t}\n\t}\n\n\treturn { warnings };\n}\n","/**\n * References validation (Level 3 progressive disclosure)\n */\n\nimport { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { ReferenceNesting } from '../types.js';\n\nexport interface ReferencesValidation {\n\tfiles_found: string[];\n\tfiles_referenced: string[];\n\tmissing_files: string[];\n\torphaned_files: string[];\n\tnesting: ReferenceNesting[];\n\tmax_nesting_depth: number;\n}\n\nexport interface ReferencesWarning {\n\ttype: 'empty_directory' | 'orphaned_file' | 'nesting_depth';\n\tmessage: string;\n}\n\nexport interface ReferencesError {\n\ttype: 'missing_file';\n\tmessage: string;\n}\n\nexport interface ReferencesResult {\n\tvalidation: ReferencesValidation;\n\twarnings: ReferencesWarning[];\n\terrors: ReferencesError[];\n}\n\n/**\n * Strip fenced code blocks from content to avoid parsing example links\n */\nfunction strip_code_blocks(content: string): string {\n\t// Remove fenced code blocks (``` or ~~~)\n\treturn content.replace(/```[\\s\\S]*?```|~~~[\\s\\S]*?~~~/g, '');\n}\n\n/**\n * Check nesting depth of reference files\n */\nfunction check_reference_nesting(\n\tskill_path: string,\n\tfile_path: string,\n\tvisited: Set<string> = new Set(),\n): { depth: number; references: string[] } {\n\tif (visited.has(file_path)) {\n\t\treturn { depth: 0, references: [] };\n\t}\n\tvisited.add(file_path);\n\n\tconst full_path = join(skill_path, file_path);\n\tif (!existsSync(full_path)) {\n\t\treturn { depth: 0, references: [] };\n\t}\n\n\tconst content = readFileSync(full_path, 'utf-8');\n\t// Strip code blocks to avoid parsing example links inside them\n\tconst content_without_code = strip_code_blocks(content);\n\tconst reference_pattern = /\\[([^\\]]+)\\]\\(([^)]+\\.md)\\)/g;\n\tconst matches = [\n\t\t...content_without_code.matchAll(reference_pattern),\n\t];\n\tconst references = matches.map((m) => m[2]);\n\n\tif (references.length === 0) {\n\t\treturn { depth: 1, references: [] };\n\t}\n\n\t// Recursively check nested references\n\tlet max_depth = 1;\n\tfor (const ref of references) {\n\t\tconst nested = check_reference_nesting(\n\t\t\tskill_path,\n\t\t\tref,\n\t\t\tnew Set(visited),\n\t\t);\n\t\tmax_depth = Math.max(max_depth, 1 + nested.depth);\n\t}\n\n\treturn { depth: max_depth, references };\n}\n\n/**\n * Validate references directory and links\n */\nexport function validate_references(\n\tskill_path: string,\n): ReferencesResult {\n\tconst references_dir = join(skill_path, 'references');\n\tconst skill_md_path = join(skill_path, 'SKILL.md');\n\n\tconst files_found: string[] = [];\n\tconst files_referenced: string[] = [];\n\tconst missing_files: string[] = [];\n\tconst nesting_data: ReferenceNesting[] = [];\n\tconst warnings: ReferencesWarning[] = [];\n\tconst errors: ReferencesError[] = [];\n\n\t// Check references directory if it exists\n\tif (existsSync(references_dir)) {\n\t\tconst files = readdirSync(references_dir);\n\t\tconst md_files = files.filter((f) => f.endsWith('.md'));\n\t\tfiles_found.push(...md_files.map((f) => `references/${f}`));\n\n\t\tif (md_files.length === 0) {\n\t\t\twarnings.push({\n\t\t\t\ttype: 'empty_directory',\n\t\t\t\tmessage: 'references/ directory exists but is empty',\n\t\t\t});\n\t\t}\n\n\t\t// Check for references in SKILL.md\n\t\tif (existsSync(skill_md_path)) {\n\t\t\tconst skill_content = readFileSync(skill_md_path, 'utf-8');\n\n\t\t\tfor (const md_file of md_files) {\n\t\t\t\tif (!skill_content.includes(md_file)) {\n\t\t\t\t\twarnings.push({\n\t\t\t\t\t\ttype: 'orphaned_file',\n\t\t\t\t\t\tmessage: `Reference file 'references/${md_file}' not mentioned in SKILL.md`,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check root-level .md files (excluding SKILL.md)\n\tif (existsSync(skill_path)) {\n\t\tconst root_files = readdirSync(skill_path);\n\t\tconst root_md_files = root_files.filter(\n\t\t\t(f) =>\n\t\t\t\tf.endsWith('.md') && f !== 'SKILL.md' && f !== 'README.md',\n\t\t);\n\t\tfiles_found.push(...root_md_files);\n\n\t\tif (existsSync(skill_md_path)) {\n\t\t\tconst skill_content = readFileSync(skill_md_path, 'utf-8');\n\n\t\t\tfor (const md_file of root_md_files) {\n\t\t\t\tif (!skill_content.includes(md_file)) {\n\t\t\t\t\twarnings.push({\n\t\t\t\t\t\ttype: 'orphaned_file',\n\t\t\t\t\t\tmessage: `Root file '${md_file}' not mentioned in SKILL.md`,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Level 3 validation: Check that all referenced files exist\n\tif (existsSync(skill_md_path)) {\n\t\tconst skill_content = readFileSync(skill_md_path, 'utf-8');\n\t\t// Strip code blocks to avoid parsing example links inside them\n\t\tconst content_without_code = strip_code_blocks(skill_content);\n\n\t\t// Extract markdown links to .md files (references/ or root-level)\n\t\t// Matches: [text](file.md), [text](references/file.md), etc.\n\t\tconst reference_link_pattern = /\\[([^\\]]+)\\]\\(([^)]+\\.md)\\)/g;\n\t\tconst matches = content_without_code.matchAll(\n\t\t\treference_link_pattern,\n\t\t);\n\n\t\tfor (const match of matches) {\n\t\t\tconst link_text = match[1];\n\t\t\tconst file_path = match[2]; // e.g., \"references/examples.md\"\n\t\t\tconst full_path = join(skill_path, file_path);\n\n\t\t\tfiles_referenced.push(file_path);\n\n\t\t\tif (!existsSync(full_path)) {\n\t\t\t\tmissing_files.push(file_path);\n\t\t\t\terrors.push({\n\t\t\t\t\ttype: 'missing_file',\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t`Referenced file not found: ${file_path}\\n` +\n\t\t\t\t\t\t` → Linked from: [${link_text}]\\n` +\n\t\t\t\t\t\t` → Create the file or remove the broken link`,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\t// Check nesting depth\n\t\t\t\tconst nesting = check_reference_nesting(\n\t\t\t\t\tskill_path,\n\t\t\t\t\tfile_path,\n\t\t\t\t);\n\t\t\t\tlet warning: string | null = null;\n\n\t\t\t\tif (nesting.depth > 1) {\n\t\t\t\t\twarning = `File has depth ${nesting.depth} (recommended: 1). Keep references one level deep from SKILL.md.`;\n\t\t\t\t\twarnings.push({\n\t\t\t\t\t\ttype: 'nesting_depth',\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t`${file_path} has nesting depth ${nesting.depth} (recommended: 1)\\n` +\n\t\t\t\t\t\t\t` → Keep references one level deep from SKILL.md for clarity`,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tnesting_data.push({\n\t\t\t\t\tfile: file_path,\n\t\t\t\t\treferences: nesting.references,\n\t\t\t\t\tdepth: nesting.depth,\n\t\t\t\t\twarning,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Calculate orphaned files\n\tconst orphaned = files_found.filter(\n\t\t(f) => !files_referenced.some((ref) => ref.includes(f)),\n\t);\n\n\tconst validation: ReferencesValidation = {\n\t\tfiles_found,\n\t\tfiles_referenced,\n\t\tmissing_files,\n\t\torphaned_files: orphaned,\n\t\tnesting: nesting_data,\n\t\tmax_nesting_depth:\n\t\t\tnesting_data.length > 0\n\t\t\t\t? Math.max(...nesting_data.map((n) => n.depth))\n\t\t\t\t: 0,\n\t};\n\n\treturn { validation, warnings, errors };\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport {\n\tDESCRIPTION_MAX_LENGTH,\n\tLIMITS,\n\tNAME_MAX_LENGTH,\n} from '../constants.js';\nimport type {\n\tStructuredValidation,\n\tValidationResult,\n\tValidationStats,\n} from '../types.js';\n\n// Import validators\nimport { analyze_alignment } from '../validators/alignment-validator.js';\nimport { validate_content } from '../validators/content-validator.js';\nimport {\n\tanalyze_trigger_phrase,\n\tanalyze_user_phrasing,\n\tvalidate_description_content,\n} from '../validators/description-validator.js';\nimport {\n\tvalidate_assets,\n\tvalidate_directory,\n\tvalidate_path_formats,\n\tvalidate_scripts,\n} from '../validators/file-structure-validator.js';\nimport {\n\textract_frontmatter,\n\tvalidate_frontmatter_structure,\n\tvalidate_hard_limits,\n\tvalidate_name_format,\n} from '../validators/frontmatter-validator.js';\nimport { validate_dependencies } from '../validators/dependency-validator.js';\nimport { validate_references } from '../validators/references-validator.js';\n\nimport type { ValidationMode } from '../types.js';\n\nexport interface ValidatorOptions {\n\tmode?: ValidationMode;\n}\n\nexport class SkillValidator {\n\tprivate skill_path: string;\n\tprivate options: ValidatorOptions;\n\tprivate errors: string[] = [];\n\tprivate warnings: string[] = [];\n\tprivate stats: ValidationStats = {\n\t\tword_count: 0,\n\t\testimated_tokens: 0,\n\t\tline_count: 0,\n\t\tdescription_length: 0,\n\t\tdescription_tokens: 0,\n\t\tcode_blocks: 0,\n\t\tsections: 0,\n\t\tlong_paragraphs: 0,\n\t};\n\n\t// Structured validation data\n\tprivate structured_validation: StructuredValidation = {\n\t\thard_limits: {\n\t\t\tname: {\n\t\t\t\tlength: 0,\n\t\t\t\tlimit: NAME_MAX_LENGTH,\n\t\t\t\tvalid: true,\n\t\t\t\terror: null,\n\t\t\t},\n\t\t\tdescription: {\n\t\t\t\tlength: 0,\n\t\t\t\tlimit: DESCRIPTION_MAX_LENGTH,\n\t\t\t\tvalid: true,\n\t\t\t\terror: null,\n\t\t\t},\n\t\t},\n\t\tname_format: {\n\t\t\tname: '',\n\t\t\tformat_valid: true,\n\t\t\tdirectory_name: '',\n\t\t\tmatches_directory: true,\n\t\t\terrors: [],\n\t\t},\n\t\tyaml_validation: {\n\t\t\tvalid: true,\n\t\t\thas_frontmatter: false,\n\t\t\tparse_error: null,\n\t\t\tmissing_fields: [],\n\t\t},\n\t\tpath_format: {\n\t\t\tinvalid_paths: [],\n\t\t},\n\t\ttriggering: {\n\t\t\ttrigger_phrase: {\n\t\t\t\thas_explicit_trigger: false,\n\t\t\t\ttrigger_phrase: null,\n\t\t\t\ttrigger_type: 'missing',\n\t\t\t},\n\t\t\tuser_phrasing: {\n\t\t\t\tstyle_checks: {\n\t\t\t\t\tis_third_person: true,\n\t\t\t\t\tuses_gerund_form: true,\n\t\t\t\t\tis_action_oriented: true,\n\t\t\t\t},\n\t\t\t\tissues: [],\n\t\t\t},\n\t\t\tkeywords: {\n\t\t\t\tdescription_keywords: [],\n\t\t\t\tcontent_keywords: [],\n\t\t\t\toverlap: [],\n\t\t\t\tdescription_only: [],\n\t\t\t\tcontent_only: [],\n\t\t\t},\n\t\t\talignment: {\n\t\t\t\tseverity: 'good',\n\t\t\t\tdescription_focus: [],\n\t\t\t\tcontent_focus: [],\n\t\t\t\tmatches: [],\n\t\t\t\tmismatches: [],\n\t\t\t\texplanation: '',\n\t\t\t},\n\t\t},\n\t};\n\n\tconstructor(skill_path: string, options: ValidatorOptions = {}) {\n\t\tthis.skill_path = skill_path;\n\t\tthis.options = options;\n\t}\n\n\tprivate error(msg: string): void {\n\t\tthis.errors.push(`❌ ${msg}`);\n\t}\n\n\tprivate warning(msg: string): void {\n\t\tthis.warnings.push(`⚠️ ${msg}`);\n\t}\n\n\tprivate validate_skill_md(): boolean {\n\t\tconst skill_md_path = join(this.skill_path, 'SKILL.md');\n\n\t\tif (!existsSync(skill_md_path)) {\n\t\t\tthis.error('SKILL.md file not found');\n\t\t\treturn false;\n\t\t}\n\n\t\tconst content = readFileSync(skill_md_path, 'utf-8');\n\n\t\t// Validate path formats (no Windows backslashes)\n\t\tconst path_format_result = validate_path_formats(content);\n\t\tthis.structured_validation.path_format =\n\t\t\tpath_format_result.validation;\n\t\tpath_format_result.errors.forEach((err) =>\n\t\t\tthis.error(err.message),\n\t\t);\n\n\t\t// Validate frontmatter structure\n\t\tconst frontmatter_validation =\n\t\t\tvalidate_frontmatter_structure(content);\n\t\tthis.structured_validation.yaml_validation =\n\t\t\tfrontmatter_validation;\n\n\t\tif (!frontmatter_validation.valid) {\n\t\t\tif (frontmatter_validation.parse_error) {\n\t\t\t\tthis.error(frontmatter_validation.parse_error);\n\t\t\t}\n\t\t\tfrontmatter_validation.missing_fields.forEach((field) => {\n\t\t\t\tthis.error(`SKILL.md frontmatter missing '${field}' field`);\n\t\t\t});\n\t\t\treturn false;\n\t\t}\n\n\t\t// Warn about unknown frontmatter fields\n\t\tif (frontmatter_validation.unknown_fields?.length) {\n\t\t\tfor (const field of frontmatter_validation.unknown_fields) {\n\t\t\t\tthis.warning(\n\t\t\t\t\t`Unknown frontmatter field '${field}'\\n` +\n\t\t\t\t\t\t` → See https://code.claude.com/docs/en/skills#frontmatter-reference`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Warn about invalid field values\n\t\tif (frontmatter_validation.field_value_warnings?.length) {\n\t\t\tfor (const warn of frontmatter_validation.field_value_warnings) {\n\t\t\t\tthis.warning(warn);\n\t\t\t}\n\t\t}\n\n\t\t// Extract frontmatter data\n\t\tconst { name, description, body, description_is_multiline } =\n\t\t\textract_frontmatter(content);\n\n\t\tif (!name || !description) {\n\t\t\tthis.error(\n\t\t\t\t'Failed to extract name or description from frontmatter',\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\n\t\t// Warn if description spans multiple lines\n\t\tif (description_is_multiline) {\n\t\t\tthis.warning(\n\t\t\t\t`Multi-line description detected. Claude Code cannot recognize skills with multi-line descriptions.\\n` +\n\t\t\t\t\t` → Run 'claude-skills-cli doctor ${this.skill_path}' to fix automatically`,\n\t\t\t);\n\t\t}\n\n\t\t// Get directory name (normalize path to handle trailing slashes)\n\t\tconst normalized_path = this.skill_path.replace(/\\/+$/, '');\n\t\tconst dir_name = normalized_path.split('/').pop() || '';\n\n\t\t// Validate name format\n\t\tconst name_validation = validate_name_format(name, dir_name);\n\t\tthis.structured_validation.name_format = name_validation;\n\t\tname_validation.errors.forEach((err) => this.error(err));\n\n\t\t// Validate hard limits\n\t\tconst hard_limits = validate_hard_limits(name, description);\n\t\tthis.structured_validation.hard_limits = hard_limits;\n\n\t\tif (!hard_limits.name.valid && hard_limits.name.error) {\n\t\t\tthis.error(hard_limits.name.error);\n\t\t}\n\n\t\tif (\n\t\t\t!hard_limits.description.valid &&\n\t\t\thard_limits.description.error\n\t\t) {\n\t\t\tthis.error(hard_limits.description.error);\n\t\t}\n\n\t\t// Validate description content\n\t\tconst desc_validation = validate_description_content(description);\n\t\tthis.stats.description_length =\n\t\t\tdesc_validation.stats.description_length;\n\t\tthis.stats.description_tokens =\n\t\t\tdesc_validation.stats.description_tokens;\n\t\tdesc_validation.errors.forEach((err) => this.error(err.message));\n\t\tdesc_validation.warnings.forEach((warn) =>\n\t\t\tthis.warning(warn.message),\n\t\t);\n\n\t\t// Analyze trigger phrase\n\t\tconst trigger_analysis = analyze_trigger_phrase(description);\n\t\tthis.structured_validation.triggering!.trigger_phrase =\n\t\t\ttrigger_analysis;\n\n\t\tif (!trigger_analysis.has_explicit_trigger) {\n\t\t\tthis.warning(\n\t\t\t\t`Description missing explicit trigger phrase ('Use when...', 'Use for...', 'Use to...')\\n` +\n\t\t\t\t\t` → Help Claude know when to activate this skill`,\n\t\t\t);\n\t\t}\n\n\t\t// Analyze user phrasing\n\t\tconst {\n\t\t\tanalysis: phrasing_analysis,\n\t\t\twarnings: phrasing_warnings,\n\t\t} = analyze_user_phrasing(description);\n\t\tthis.structured_validation.triggering!.user_phrasing =\n\t\t\tphrasing_analysis;\n\t\tphrasing_warnings.forEach((warn) => this.warning(warn.message));\n\n\t\t// Analyze alignment\n\t\tconst alignment_result = analyze_alignment(description, body);\n\t\tthis.structured_validation.triggering!.keywords =\n\t\t\talignment_result.keywords;\n\t\tthis.structured_validation.triggering!.alignment =\n\t\t\talignment_result.alignment;\n\t\talignment_result.warnings.forEach((warn) =>\n\t\t\tthis.warning(warn.message),\n\t\t);\n\n\t\t// Validate content (progressive disclosure)\n\t\tconst content_validation = validate_content(body, {\n\t\t\tmode: this.options.mode,\n\t\t});\n\t\tthis.stats.word_count = content_validation.stats.word_count;\n\t\tthis.stats.estimated_tokens =\n\t\t\tcontent_validation.stats.estimated_tokens;\n\t\tthis.stats.line_count = content_validation.stats.line_count;\n\t\tthis.stats.code_blocks = content_validation.stats.code_blocks;\n\t\tthis.stats.sections = content_validation.stats.sections;\n\t\tthis.stats.long_paragraphs =\n\t\t\tcontent_validation.stats.long_paragraphs;\n\t\tcontent_validation.errors.forEach((err) =>\n\t\t\tthis.error(err.message),\n\t\t);\n\t\tcontent_validation.warnings.forEach((warn) =>\n\t\t\tthis.warning(warn.message),\n\t\t);\n\n\t\t// Validate dependencies (depends-on-* fields)\n\t\tconst parts = content.split('---\\n');\n\t\tif (parts.length >= 3) {\n\t\t\tconst frontmatter_raw = parts[1];\n\t\t\tconst dep_result = validate_dependencies(frontmatter_raw);\n\t\t\tdep_result.errors.forEach((err) => this.error(err));\n\t\t\tdep_result.warnings.forEach((warn) => this.warning(warn));\n\t\t\tthis.structured_validation.dependency_validation =\n\t\t\t\tdep_result.validation;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tpublic validate_all(): ValidationResult {\n\t\t// Validate directory\n\t\tconst dir_result = validate_directory(this.skill_path);\n\t\tif (!dir_result.valid) {\n\t\t\tdir_result.errors.forEach((err) => this.error(err.message));\n\t\t\treturn {\n\t\t\t\terrors: this.errors,\n\t\t\t\twarnings: this.warnings,\n\t\t\t\tis_valid: false,\n\t\t\t\tstats: this.stats,\n\t\t\t\tvalidation: this.structured_validation,\n\t\t\t};\n\t\t}\n\n\t\t// Validate SKILL.md\n\t\tthis.validate_skill_md();\n\n\t\t// Validate references\n\t\tconst refs_result = validate_references(this.skill_path);\n\t\trefs_result.errors.forEach((err) => this.error(err.message));\n\t\trefs_result.warnings.forEach((warn) =>\n\t\t\tthis.warning(warn.message),\n\t\t);\n\n\t\t// Validate scripts\n\t\tconst scripts_result = validate_scripts(this.skill_path);\n\t\tscripts_result.warnings.forEach((warn) =>\n\t\t\tthis.warning(warn.message),\n\t\t);\n\n\t\t// Validate assets\n\t\tconst assets_result = validate_assets(this.skill_path);\n\t\tassets_result.warnings.forEach((warn) =>\n\t\t\tthis.warning(warn.message),\n\t\t);\n\n\t\t// Populate progressive disclosure structured validation\n\t\tconst mode_limits = LIMITS[this.options.mode || 'strict'];\n\t\tconst line_limit = mode_limits.lines.max;\n\t\tconst word_limit = mode_limits.words.max;\n\t\tthis.structured_validation.progressive_disclosure = {\n\t\t\tskill_md_size: {\n\t\t\t\tlines: this.stats.line_count,\n\t\t\t\twords: this.stats.word_count,\n\t\t\t\ttokens: this.stats.estimated_tokens,\n\t\t\t\texceeds_line_limit: this.stats.line_count > line_limit,\n\t\t\t\texceeds_word_limit: this.stats.word_count > word_limit,\n\t\t\t},\n\t\t\treferences: refs_result.validation,\n\t\t};\n\n\t\treturn {\n\t\t\terrors: this.errors,\n\t\t\twarnings: this.warnings,\n\t\t\tis_valid: this.errors.length === 0,\n\t\t\tstats: this.stats,\n\t\t\tvalidation: this.structured_validation,\n\t\t};\n\t}\n\n\tpublic get_errors(): string[] {\n\t\treturn this.errors;\n\t}\n\n\tpublic get_warnings(): string[] {\n\t\treturn this.warnings;\n\t}\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,56 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-skills-cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
4
|
"description": "CLI toolkit for creating and managing Claude Agent Skills",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./dist/index.js",
|
|
7
|
-
"bin": {
|
|
8
|
-
"claude-skills-cli": "./dist/index.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"dist",
|
|
12
|
-
"docs",
|
|
13
|
-
"README.md"
|
|
14
|
-
],
|
|
15
|
-
"engines": {
|
|
16
|
-
"node": ">=22.0.0"
|
|
17
|
-
},
|
|
18
5
|
"keywords": [
|
|
19
|
-
"claude",
|
|
20
|
-
"skills",
|
|
21
|
-
"cli",
|
|
22
6
|
"agent",
|
|
23
7
|
"anthropic",
|
|
24
|
-
"claude
|
|
8
|
+
"claude",
|
|
9
|
+
"claude-code",
|
|
10
|
+
"cli",
|
|
11
|
+
"skills"
|
|
25
12
|
],
|
|
26
|
-
"
|
|
13
|
+
"homepage": "https://github.com/spences10/claude-skills-cli#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/spences10/claude-skills-cli/issues"
|
|
16
|
+
},
|
|
27
17
|
"license": "MIT",
|
|
18
|
+
"author": "Scott Spence",
|
|
28
19
|
"repository": {
|
|
29
20
|
"type": "git",
|
|
30
|
-
"url": "https://github.com/spences10/claude-skills-cli.git"
|
|
21
|
+
"url": "git+https://github.com/spences10/claude-skills-cli.git"
|
|
31
22
|
},
|
|
32
|
-
"
|
|
33
|
-
"
|
|
23
|
+
"bin": {
|
|
24
|
+
"claude-skills-cli": "./dist/index.js"
|
|
34
25
|
},
|
|
35
|
-
"
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"docs",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"type": "module",
|
|
32
|
+
"main": "./dist/index.js",
|
|
36
33
|
"dependencies": {
|
|
37
|
-
"
|
|
38
|
-
"chalk": "^5.6.2",
|
|
39
|
-
"citty": "^0.2.0"
|
|
34
|
+
"citty": "^0.2.2"
|
|
40
35
|
},
|
|
41
36
|
"devDependencies": {
|
|
42
|
-
"@changesets/cli": "^2.
|
|
43
|
-
"@types/
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
|
|
37
|
+
"@changesets/cli": "^2.30.0",
|
|
38
|
+
"@types/node": "^25.6.0",
|
|
39
|
+
"vite-plus": "^0.1.16",
|
|
40
|
+
"vitest": "^4.1.4"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=22.0.0"
|
|
47
44
|
},
|
|
48
45
|
"scripts": {
|
|
49
|
-
"build": "
|
|
50
|
-
"dev": "
|
|
46
|
+
"build": "vp pack",
|
|
47
|
+
"dev": "vp pack --watch",
|
|
51
48
|
"start": "node ./dist/index.js",
|
|
52
|
-
"
|
|
53
|
-
"format
|
|
49
|
+
"check": "vp check",
|
|
50
|
+
"format": "vp check --fix",
|
|
51
|
+
"test": "vp test",
|
|
52
|
+
"test:watch": "vp test watch",
|
|
54
53
|
"changeset": "changeset",
|
|
55
54
|
"version": "changeset version",
|
|
56
55
|
"release": "pnpm run build && changeset publish"
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { defineCommand } from 'citty';
|
|
2
|
-
import { add_hook_command } from './add-hook.js';
|
|
3
|
-
export default defineCommand({
|
|
4
|
-
meta: {
|
|
5
|
-
name: 'add-hook',
|
|
6
|
-
description: 'Add skill activation hook to .claude/settings.json',
|
|
7
|
-
},
|
|
8
|
-
args: {
|
|
9
|
-
local: {
|
|
10
|
-
type: 'boolean',
|
|
11
|
-
description: 'Install in project .claude/settings.local.json',
|
|
12
|
-
},
|
|
13
|
-
project: {
|
|
14
|
-
type: 'boolean',
|
|
15
|
-
description: 'Install in project .claude/settings.json',
|
|
16
|
-
},
|
|
17
|
-
type: {
|
|
18
|
-
type: 'string',
|
|
19
|
-
description: 'Hook type: simple-inline|simple-script|forced-eval|llm-eval',
|
|
20
|
-
},
|
|
21
|
-
force: {
|
|
22
|
-
type: 'boolean',
|
|
23
|
-
description: 'Replace existing hook without prompting',
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
run({ args }) {
|
|
27
|
-
add_hook_command({
|
|
28
|
-
local: args.local,
|
|
29
|
-
project: args.project,
|
|
30
|
-
type: args.type,
|
|
31
|
-
force: args.force,
|
|
32
|
-
});
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
|
-
//# sourceMappingURL=add-hook.cmd.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"add-hook.cmd.js","sourceRoot":"","sources":["../../src/commands/add-hook.cmd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,eAAe,aAAa,CAAC;IAC5B,IAAI,EAAE;QACL,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,oDAAoD;KACjE;IACD,IAAI,EAAE;QACL,KAAK,EAAE;YACN,IAAI,EAAE,SAAS;YACf,WAAW,EAAE,gDAAgD;SAC7D;QACD,OAAO,EAAE;YACR,IAAI,EAAE,SAAS;YACf,WAAW,EAAE,0CAA0C;SACvD;QACD,IAAI,EAAE;YACL,IAAI,EAAE,QAAQ;YACd,WAAW,EACV,6DAA6D;SAC9D;QACD,KAAK,EAAE;YACN,IAAI,EAAE,SAAS;YACf,WAAW,EAAE,yCAAyC;SACtD;KACD;IACD,GAAG,CAAC,EAAE,IAAI,EAAE;QACX,gBAAgB,CAAC;YAChB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,IAAI,EAAE,IAAI,CAAC,IAKC;YACZ,KAAK,EAAE,IAAI,CAAC,KAAK;SACjB,CAAC,CAAC;IACJ,CAAC;CACD,CAAC,CAAC"}
|
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { FORCED_EVAL_HOOK_TEMPLATE, LLM_EVAL_HOOK_TEMPLATE, SIMPLE_HOOK_TEMPLATE, } from '../core/templates.js';
|
|
5
|
-
import { ensure_dir, make_executable } from '../utils/fs.js';
|
|
6
|
-
import { error, info, success, warning } from '../utils/output.js';
|
|
7
|
-
const HOOK_TYPES = {
|
|
8
|
-
'simple-inline': {
|
|
9
|
-
name: 'Simple Inline',
|
|
10
|
-
success_rate: '20%',
|
|
11
|
-
description: 'Echo command in settings.json',
|
|
12
|
-
command: "echo 'INSTRUCTION: If the prompt matches any available skill keywords, use Skill(skill-name) to activate it.'",
|
|
13
|
-
script: null,
|
|
14
|
-
},
|
|
15
|
-
'simple-script': {
|
|
16
|
-
name: 'Simple Script',
|
|
17
|
-
success_rate: '20%',
|
|
18
|
-
description: 'Script file with basic instruction',
|
|
19
|
-
command: null,
|
|
20
|
-
script: 'skill-activation-simple.sh',
|
|
21
|
-
template: SIMPLE_HOOK_TEMPLATE,
|
|
22
|
-
},
|
|
23
|
-
'forced-eval': {
|
|
24
|
-
name: 'Forced Evaluation',
|
|
25
|
-
success_rate: '84%',
|
|
26
|
-
description: 'Mandatory 3-step evaluation process',
|
|
27
|
-
command: null,
|
|
28
|
-
script: 'skill-activation-forced-eval.sh',
|
|
29
|
-
template: FORCED_EVAL_HOOK_TEMPLATE,
|
|
30
|
-
},
|
|
31
|
-
'llm-eval': {
|
|
32
|
-
name: 'LLM Evaluation',
|
|
33
|
-
success_rate: '80%',
|
|
34
|
-
description: 'Claude API pre-evaluation (requires ANTHROPIC_API_KEY)',
|
|
35
|
-
command: null,
|
|
36
|
-
script: 'skill-activation-llm-eval.sh',
|
|
37
|
-
template: LLM_EVAL_HOOK_TEMPLATE,
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
export function add_hook_command(options = {}) {
|
|
41
|
-
// Default to forced-eval for best performance
|
|
42
|
-
const hook_type = (options.type ||
|
|
43
|
-
'forced-eval');
|
|
44
|
-
if (!HOOK_TYPES[hook_type]) {
|
|
45
|
-
error(`Invalid hook type: ${hook_type}`);
|
|
46
|
-
info('Valid types: simple-inline, simple-script, forced-eval, llm-eval');
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
const hook_config = HOOK_TYPES[hook_type];
|
|
50
|
-
// Determine which settings file to use
|
|
51
|
-
let settings_path;
|
|
52
|
-
let hooks_dir;
|
|
53
|
-
let scope;
|
|
54
|
-
if (options.local) {
|
|
55
|
-
// Project-specific local (gitignored)
|
|
56
|
-
settings_path = join('.claude', 'settings.local.json');
|
|
57
|
-
hooks_dir = join('.claude', 'hooks');
|
|
58
|
-
scope = 'project-local';
|
|
59
|
-
}
|
|
60
|
-
else if (options.project) {
|
|
61
|
-
// Project-specific shared (committed)
|
|
62
|
-
settings_path = join('.claude', 'settings.json');
|
|
63
|
-
hooks_dir = join('.claude', 'hooks');
|
|
64
|
-
scope = 'project';
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
// Global (default)
|
|
68
|
-
settings_path = join(homedir(), '.claude', 'settings.json');
|
|
69
|
-
hooks_dir = join(homedir(), '.claude', 'hooks');
|
|
70
|
-
scope = 'global';
|
|
71
|
-
}
|
|
72
|
-
let settings = {};
|
|
73
|
-
// Check if settings.json exists and load it
|
|
74
|
-
if (existsSync(settings_path)) {
|
|
75
|
-
try {
|
|
76
|
-
const content = readFileSync(settings_path, 'utf-8');
|
|
77
|
-
settings = JSON.parse(content);
|
|
78
|
-
// Warn if all hooks are disabled
|
|
79
|
-
if (settings.disableAllHooks) {
|
|
80
|
-
warning('disableAllHooks is set to true in settings — hooks will not run');
|
|
81
|
-
}
|
|
82
|
-
// Check if UserPromptSubmit hook already exists
|
|
83
|
-
if (settings.hooks?.UserPromptSubmit &&
|
|
84
|
-
Array.isArray(settings.hooks.UserPromptSubmit) &&
|
|
85
|
-
settings.hooks.UserPromptSubmit.length > 0) {
|
|
86
|
-
// Get the first (and should be only) UserPromptSubmit object
|
|
87
|
-
const userPromptSubmit = settings.hooks.UserPromptSubmit[0];
|
|
88
|
-
// Find existing skill activation hook (check for various patterns)
|
|
89
|
-
const existing_hook = userPromptSubmit.hooks?.find((h) => h.type === 'command' &&
|
|
90
|
-
h.command &&
|
|
91
|
-
(h.command.includes('skill-activation') ||
|
|
92
|
-
h.command.includes('skill-forced-eval-hook') ||
|
|
93
|
-
h.command.includes('skill-llm-eval-hook') ||
|
|
94
|
-
h.command.includes('skill-simple-instruction-hook') ||
|
|
95
|
-
h.command.includes('If the prompt matches any available skill keywords')));
|
|
96
|
-
if (existing_hook) {
|
|
97
|
-
warning(`Skill activation hook already exists in ${scope} settings`);
|
|
98
|
-
info(`Current hook: ${existing_hook.command || existing_hook.prompt || 'unknown'}`);
|
|
99
|
-
console.log('');
|
|
100
|
-
if (options.force) {
|
|
101
|
-
info('--force flag provided, replacing existing hook...');
|
|
102
|
-
// Remove the existing hook
|
|
103
|
-
userPromptSubmit.hooks = userPromptSubmit.hooks?.filter((h) => h !== existing_hook);
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
info('No changes made.');
|
|
107
|
-
info('To replace, run with --force flag or manually remove the existing hook.');
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
catch (err) {
|
|
114
|
-
error(`Failed to parse ${settings_path}: ${err}`);
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
// Determine the hook handler to use
|
|
119
|
-
let hook_handler;
|
|
120
|
-
if (hook_config.script) {
|
|
121
|
-
// Script-based hook: create the script file
|
|
122
|
-
const script_path = join(hooks_dir, hook_config.script);
|
|
123
|
-
info(`Creating ${hook_config.name} hook script...`);
|
|
124
|
-
try {
|
|
125
|
-
ensure_dir(hooks_dir);
|
|
126
|
-
// Write the script file
|
|
127
|
-
if (hook_config.template) {
|
|
128
|
-
writeFileSync(script_path, hook_config.template(), 'utf-8');
|
|
129
|
-
}
|
|
130
|
-
// Make it executable
|
|
131
|
-
make_executable(script_path);
|
|
132
|
-
success(`Script created: ${script_path}`);
|
|
133
|
-
}
|
|
134
|
-
catch (err) {
|
|
135
|
-
error(`Failed to create hook script: ${err}`);
|
|
136
|
-
process.exit(1);
|
|
137
|
-
}
|
|
138
|
-
// Use relative path for project hooks, absolute for global
|
|
139
|
-
const command = scope === 'global'
|
|
140
|
-
? script_path
|
|
141
|
-
: `.claude/hooks/${hook_config.script}`;
|
|
142
|
-
hook_handler = { type: 'command', command };
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
// Inline command
|
|
146
|
-
hook_handler = { type: 'command', command: hook_config.command };
|
|
147
|
-
}
|
|
148
|
-
// Update or create settings.json
|
|
149
|
-
if (existsSync(settings_path)) {
|
|
150
|
-
// Add to existing settings
|
|
151
|
-
const userPromptSubmit = settings.hooks?.UserPromptSubmit?.[0];
|
|
152
|
-
if (userPromptSubmit) {
|
|
153
|
-
// Add to existing hooks array
|
|
154
|
-
if (!userPromptSubmit.hooks) {
|
|
155
|
-
userPromptSubmit.hooks = [];
|
|
156
|
-
}
|
|
157
|
-
userPromptSubmit.hooks.push(hook_handler);
|
|
158
|
-
info(`Adding ${hook_config.name} hook to existing ${scope} settings...`);
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
// Create UserPromptSubmit section
|
|
162
|
-
settings.hooks = settings.hooks || {};
|
|
163
|
-
settings.hooks.UserPromptSubmit = [
|
|
164
|
-
{
|
|
165
|
-
hooks: [hook_handler],
|
|
166
|
-
},
|
|
167
|
-
];
|
|
168
|
-
info(`Adding ${hook_config.name} hook to ${scope} settings...`);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
// Create new settings.json
|
|
173
|
-
info(`Creating ${scope} settings with ${hook_config.name} hook...`);
|
|
174
|
-
settings = {
|
|
175
|
-
hooks: {
|
|
176
|
-
UserPromptSubmit: [
|
|
177
|
-
{
|
|
178
|
-
hooks: [hook_handler],
|
|
179
|
-
},
|
|
180
|
-
],
|
|
181
|
-
},
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
// Write settings.json
|
|
185
|
-
try {
|
|
186
|
-
ensure_dir(scope === 'global' ? join(homedir(), '.claude') : '.claude');
|
|
187
|
-
writeFileSync(settings_path, JSON.stringify(settings, null, 2), 'utf-8');
|
|
188
|
-
success(`${hook_config.name} hook added successfully! (${scope})`);
|
|
189
|
-
console.log('');
|
|
190
|
-
info(`Settings: ${settings_path}`);
|
|
191
|
-
if (hook_config.script) {
|
|
192
|
-
info(`Script: ${join(hooks_dir, hook_config.script)}`);
|
|
193
|
-
}
|
|
194
|
-
console.log('');
|
|
195
|
-
info(`Hook Type: ${hook_config.name}`);
|
|
196
|
-
info(`Success Rate: ${hook_config.success_rate}`);
|
|
197
|
-
info(`Description: ${hook_config.description}`);
|
|
198
|
-
console.log('');
|
|
199
|
-
if (hook_type === 'llm-eval') {
|
|
200
|
-
warning('LLM eval hook requires ANTHROPIC_API_KEY environment variable');
|
|
201
|
-
info('Set with: export ANTHROPIC_API_KEY=your-key-here');
|
|
202
|
-
info('Falls back to simple instruction if API key not found');
|
|
203
|
-
console.log('');
|
|
204
|
-
}
|
|
205
|
-
warning('Restart Claude Code for hooks to take effect (hooks are captured at startup)');
|
|
206
|
-
console.log('');
|
|
207
|
-
info('Next steps:');
|
|
208
|
-
console.log(' 1. Create skills with: claude-skills-cli init --name <name>');
|
|
209
|
-
console.log(' 2. Validate with: claude-skills-cli validate <path>');
|
|
210
|
-
}
|
|
211
|
-
catch (err) {
|
|
212
|
-
error(`Failed to write ${settings_path}: ${err}`);
|
|
213
|
-
process.exit(1);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
//# sourceMappingURL=add-hook.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"add-hook.js","sourceRoot":"","sources":["../../src/commands/add-hook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EACN,yBAAyB,EACzB,sBAAsB,EACtB,oBAAoB,GACpB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAoBnE,MAAM,UAAU,GAAG;IAClB,eAAe,EAAE;QAChB,IAAI,EAAE,eAAe;QACrB,YAAY,EAAE,KAAK;QACnB,WAAW,EAAE,+BAA+B;QAC5C,OAAO,EACN,+GAA+G;QAChH,MAAM,EAAE,IAAI;KACZ;IACD,eAAe,EAAE;QAChB,IAAI,EAAE,eAAe;QACrB,YAAY,EAAE,KAAK;QACnB,WAAW,EAAE,oCAAoC;QACjD,OAAO,EAAE,IAAI;QACb,MAAM,EAAE,4BAA4B;QACpC,QAAQ,EAAE,oBAAoB;KAC9B;IACD,aAAa,EAAE;QACd,IAAI,EAAE,mBAAmB;QACzB,YAAY,EAAE,KAAK;QACnB,WAAW,EAAE,qCAAqC;QAClD,OAAO,EAAE,IAAI;QACb,MAAM,EAAE,iCAAiC;QACzC,QAAQ,EAAE,yBAAyB;KACnC;IACD,UAAU,EAAE;QACX,IAAI,EAAE,gBAAgB;QACtB,YAAY,EAAE,KAAK;QACnB,WAAW,EACV,wDAAwD;QACzD,OAAO,EAAE,IAAI;QACb,MAAM,EAAE,8BAA8B;QACtC,QAAQ,EAAE,sBAAsB;KAChC;CACQ,CAAC;AAIX,MAAM,UAAU,gBAAgB,CAAC,UAA0B,EAAE;IAC5D,8CAA8C;IAC9C,MAAM,SAAS,GAAa,CAAC,OAAO,CAAC,IAAI;QACxC,aAAa,CAAa,CAAC;IAE5B,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5B,KAAK,CAAC,sBAAsB,SAAS,EAAE,CAAC,CAAC;QACzC,IAAI,CACH,kEAAkE,CAClE,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;IAED,MAAM,WAAW,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IAE1C,uCAAuC;IACvC,IAAI,aAAqB,CAAC;IAC1B,IAAI,SAAiB,CAAC;IACtB,IAAI,KAAa,CAAC;IAElB,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QACnB,sCAAsC;QACtC,aAAa,GAAG,IAAI,CAAC,SAAS,EAAE,qBAAqB,CAAC,CAAC;QACvD,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACrC,KAAK,GAAG,eAAe,CAAC;IACzB,CAAC;SAAM,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QAC5B,sCAAsC;QACtC,aAAa,GAAG,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;QACjD,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACrC,KAAK,GAAG,SAAS,CAAC;IACnB,CAAC;SAAM,CAAC;QACP,mBAAmB;QACnB,aAAa,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;QAC5D,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QAChD,KAAK,GAAG,QAAQ,CAAC;IAClB,CAAC;IAED,IAAI,QAAQ,GAAiB,EAAE,CAAC;IAEhC,4CAA4C;IAC5C,IAAI,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAC/B,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;YACrD,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAE/B,iCAAiC;YACjC,IAAI,QAAQ,CAAC,eAAe,EAAE,CAAC;gBAC9B,OAAO,CACN,iEAAiE,CACjE,CAAC;YACH,CAAC;YAED,gDAAgD;YAChD,IACC,QAAQ,CAAC,KAAK,EAAE,gBAAgB;gBAChC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,gBAAgB,CAAC;gBAC9C,QAAQ,CAAC,KAAK,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,EACzC,CAAC;gBACF,6DAA6D;gBAC7D,MAAM,gBAAgB,GAAG,QAAQ,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;gBAE5D,mEAAmE;gBACnE,MAAM,aAAa,GAAG,gBAAgB,CAAC,KAAK,EAAE,IAAI,CACjD,CAAC,CAAC,EAAE,EAAE,CACL,CAAC,CAAC,IAAI,KAAK,SAAS;oBACpB,CAAC,CAAC,OAAO;oBACT,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC;wBACtC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAC;wBAC5C,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAC;wBACzC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,+BAA+B,CAAC;wBACnD,CAAC,CAAC,OAAO,CAAC,QAAQ,CACjB,oDAAoD,CACpD,CAAC,CACJ,CAAC;gBAEF,IAAI,aAAa,EAAE,CAAC;oBACnB,OAAO,CACN,2CAA2C,KAAK,WAAW,CAC3D,CAAC;oBACF,IAAI,CACH,iBAAiB,aAAa,CAAC,OAAO,IAAI,aAAa,CAAC,MAAM,IAAI,SAAS,EAAE,CAC7E,CAAC;oBACF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;oBAEhB,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;wBACnB,IAAI,CAAC,mDAAmD,CAAC,CAAC;wBAC1D,2BAA2B;wBAC3B,gBAAgB,CAAC,KAAK,GAAG,gBAAgB,CAAC,KAAK,EAAE,MAAM,CACtD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,aAAa,CAC1B,CAAC;oBACH,CAAC;yBAAM,CAAC;wBACP,IAAI,CAAC,kBAAkB,CAAC,CAAC;wBACzB,IAAI,CACH,yEAAyE,CACzE,CAAC;wBACF,OAAO;oBACR,CAAC;gBACF,CAAC;YACF,CAAC;QACF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,KAAK,CAAC,mBAAmB,aAAa,KAAK,GAAG,EAAE,CAAC,CAAC;YAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;IACF,CAAC;IAED,oCAAoC;IACpC,IAAI,YAAyB,CAAC;IAE9B,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC;QACxB,4CAA4C;QAC5C,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;QAExD,IAAI,CAAC,YAAY,WAAW,CAAC,IAAI,iBAAiB,CAAC,CAAC;QAEpD,IAAI,CAAC;YACJ,UAAU,CAAC,SAAS,CAAC,CAAC;YAEtB,wBAAwB;YACxB,IAAI,WAAW,CAAC,QAAQ,EAAE,CAAC;gBAC1B,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,QAAQ,EAAE,EAAE,OAAO,CAAC,CAAC;YAC7D,CAAC;YAED,qBAAqB;YACrB,eAAe,CAAC,WAAW,CAAC,CAAC;YAE7B,OAAO,CAAC,mBAAmB,WAAW,EAAE,CAAC,CAAC;QAC3C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,KAAK,CAAC,iCAAiC,GAAG,EAAE,CAAC,CAAC;YAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;QAED,2DAA2D;QAC3D,MAAM,OAAO,GACZ,KAAK,KAAK,QAAQ;YACjB,CAAC,CAAC,WAAW;YACb,CAAC,CAAC,iBAAiB,WAAW,CAAC,MAAM,EAAE,CAAC;QAC1C,YAAY,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;IAC7C,CAAC;SAAM,CAAC;QACP,iBAAiB;QACjB,YAAY,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,CAAC,OAAQ,EAAE,CAAC;IACnE,CAAC;IAED,iCAAiC;IACjC,IAAI,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAC/B,2BAA2B;QAC3B,MAAM,gBAAgB,GAAG,QAAQ,CAAC,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,CAAC;QAE/D,IAAI,gBAAgB,EAAE,CAAC;YACtB,8BAA8B;YAC9B,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;gBAC7B,gBAAgB,CAAC,KAAK,GAAG,EAAE,CAAC;YAC7B,CAAC;YACD,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAE1C,IAAI,CACH,UAAU,WAAW,CAAC,IAAI,qBAAqB,KAAK,cAAc,CAClE,CAAC;QACH,CAAC;aAAM,CAAC;YACP,kCAAkC;YAClC,QAAQ,CAAC,KAAK,GAAG,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC;YACtC,QAAQ,CAAC,KAAK,CAAC,gBAAgB,GAAG;gBACjC;oBACC,KAAK,EAAE,CAAC,YAAY,CAAC;iBACrB;aACD,CAAC;YAEF,IAAI,CAAC,UAAU,WAAW,CAAC,IAAI,YAAY,KAAK,cAAc,CAAC,CAAC;QACjE,CAAC;IACF,CAAC;SAAM,CAAC;QACP,2BAA2B;QAC3B,IAAI,CACH,YAAY,KAAK,kBAAkB,WAAW,CAAC,IAAI,UAAU,CAC7D,CAAC;QACF,QAAQ,GAAG;YACV,KAAK,EAAE;gBACN,gBAAgB,EAAE;oBACjB;wBACC,KAAK,EAAE,CAAC,YAAY,CAAC;qBACrB;iBACD;aACD;SACD,CAAC;IACH,CAAC;IAED,sBAAsB;IACtB,IAAI,CAAC;QACJ,UAAU,CACT,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAC3D,CAAC;QACF,aAAa,CACZ,aAAa,EACb,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EACjC,OAAO,CACP,CAAC;QACF,OAAO,CACN,GAAG,WAAW,CAAC,IAAI,8BAA8B,KAAK,GAAG,CACzD,CAAC;QACF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,IAAI,CAAC,aAAa,aAAa,EAAE,CAAC,CAAC;QACnC,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC;YACxB,IAAI,CAAC,WAAW,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACxD,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,IAAI,CAAC,cAAc,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC;QACvC,IAAI,CAAC,iBAAiB,WAAW,CAAC,YAAY,EAAE,CAAC,CAAC;QAClD,IAAI,CAAC,gBAAgB,WAAW,CAAC,WAAW,EAAE,CAAC,CAAC;QAChD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEhB,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;YAC9B,OAAO,CACN,+DAA+D,CAC/D,CAAC;YACF,IAAI,CAAC,kDAAkD,CAAC,CAAC;YACzD,IAAI,CAAC,uDAAuD,CAAC,CAAC;YAC9D,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACjB,CAAC;QAED,OAAO,CACN,8EAA8E,CAC9E,CAAC;QACF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,IAAI,CAAC,aAAa,CAAC,CAAC;QACpB,OAAO,CAAC,GAAG,CACV,+DAA+D,CAC/D,CAAC;QACF,OAAO,CAAC,GAAG,CACV,uDAAuD,CACvD,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,KAAK,CAAC,mBAAmB,aAAa,KAAK,GAAG,EAAE,CAAC,CAAC;QAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;AACF,CAAC"}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { defineCommand } from 'citty';
|
|
2
|
-
import { doctor_command } from './doctor.js';
|
|
3
|
-
export default defineCommand({
|
|
4
|
-
meta: {
|
|
5
|
-
name: 'doctor',
|
|
6
|
-
description: 'Fix common skill issues automatically',
|
|
7
|
-
},
|
|
8
|
-
args: {
|
|
9
|
-
skill_path: {
|
|
10
|
-
type: 'positional',
|
|
11
|
-
description: 'Path to skill directory',
|
|
12
|
-
required: true,
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
run({ args }) {
|
|
16
|
-
doctor_command({ skill_path: args.skill_path });
|
|
17
|
-
},
|
|
18
|
-
});
|
|
19
|
-
//# sourceMappingURL=doctor.cmd.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.cmd.js","sourceRoot":"","sources":["../../src/commands/doctor.cmd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,eAAe,aAAa,CAAC;IAC5B,IAAI,EAAE;QACL,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,uCAAuC;KACpD;IACD,IAAI,EAAE;QACL,UAAU,EAAE;YACX,IAAI,EAAE,YAAY;YAClB,WAAW,EAAE,yBAAyB;YACtC,QAAQ,EAAE,IAAI;SACd;KACD;IACD,GAAG,CAAC,EAAE,IAAI,EAAE;QACX,cAAc,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IACjD,CAAC;CACD,CAAC,CAAC"}
|
package/dist/commands/doctor.js
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { basename, join } from 'node:path';
|
|
3
|
-
import { error, info, success } from '../utils/output.js';
|
|
4
|
-
import { extract_frontmatter, is_description_multiline, } from '../validators/frontmatter-validator.js';
|
|
5
|
-
export function doctor_command(options) {
|
|
6
|
-
const { skill_path } = options;
|
|
7
|
-
const skill_name = basename(skill_path);
|
|
8
|
-
const skill_md_path = join(skill_path, 'SKILL.md');
|
|
9
|
-
info(`Running doctor on: ${skill_name}`);
|
|
10
|
-
console.log('='.repeat(60));
|
|
11
|
-
// Read SKILL.md
|
|
12
|
-
let content;
|
|
13
|
-
try {
|
|
14
|
-
content = readFileSync(skill_md_path, 'utf-8');
|
|
15
|
-
}
|
|
16
|
-
catch (err) {
|
|
17
|
-
error(`Failed to read SKILL.md: ${err}`);
|
|
18
|
-
process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
// Extract frontmatter
|
|
21
|
-
const frontmatter_data = extract_frontmatter(content);
|
|
22
|
-
if (!frontmatter_data.description_is_multiline) {
|
|
23
|
-
success('No issues found. Description is already on a single line.');
|
|
24
|
-
process.exit(0);
|
|
25
|
-
}
|
|
26
|
-
info('Found multi-line description. Fixing...');
|
|
27
|
-
// Fix the multi-line description
|
|
28
|
-
const fixed_content = fix_multiline_description(content);
|
|
29
|
-
// Write fixed content back
|
|
30
|
-
try {
|
|
31
|
-
writeFileSync(skill_md_path, fixed_content, 'utf-8');
|
|
32
|
-
success('Fixed multi-line description!');
|
|
33
|
-
console.log('\nChanges made:');
|
|
34
|
-
console.log(' • Added # prettier-ignore comment before description');
|
|
35
|
-
console.log(' • Reflowed description to single line');
|
|
36
|
-
console.log('\n✓ Run validate command to confirm the fix');
|
|
37
|
-
process.exit(0);
|
|
38
|
-
}
|
|
39
|
-
catch (err) {
|
|
40
|
-
error(`Failed to write SKILL.md: ${err}`);
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Fix multi-line description by adding prettier-ignore and reflowing to single line
|
|
46
|
-
*/
|
|
47
|
-
function fix_multiline_description(content) {
|
|
48
|
-
const lines = content.split('\n');
|
|
49
|
-
const fixed_lines = [];
|
|
50
|
-
let in_frontmatter = false;
|
|
51
|
-
let frontmatter_count = 0;
|
|
52
|
-
let in_description = false;
|
|
53
|
-
let description_parts = [];
|
|
54
|
-
let description_line_index = -1;
|
|
55
|
-
for (let i = 0; i < lines.length; i++) {
|
|
56
|
-
const line = lines[i];
|
|
57
|
-
// Track frontmatter boundaries
|
|
58
|
-
if (line.trim() === '---') {
|
|
59
|
-
frontmatter_count++;
|
|
60
|
-
// If we're closing frontmatter and still collecting description
|
|
61
|
-
if (frontmatter_count === 2 && in_description) {
|
|
62
|
-
// Write out the collected description
|
|
63
|
-
const full_description = description_parts.join(' ');
|
|
64
|
-
fixed_lines.push(`description: ${full_description}`);
|
|
65
|
-
description_parts = [];
|
|
66
|
-
in_description = false;
|
|
67
|
-
}
|
|
68
|
-
in_frontmatter = frontmatter_count === 1;
|
|
69
|
-
fixed_lines.push(line);
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
// Not in frontmatter, just pass through
|
|
73
|
-
if (!in_frontmatter) {
|
|
74
|
-
fixed_lines.push(line);
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
// Check if this is the description line
|
|
78
|
-
if (line.match(/^description:/)) {
|
|
79
|
-
// Check if it's already multi-line
|
|
80
|
-
if (!is_description_multiline(lines.slice(i).join('\n'))) {
|
|
81
|
-
// Single line, just pass through
|
|
82
|
-
fixed_lines.push(line);
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
in_description = true;
|
|
86
|
-
description_line_index = fixed_lines.length;
|
|
87
|
-
// Extract value on same line (if any)
|
|
88
|
-
const match = line.match(/^description:\s*(.*)$/);
|
|
89
|
-
const value_on_line = match ? match[1].trim() : '';
|
|
90
|
-
if (value_on_line) {
|
|
91
|
-
description_parts.push(value_on_line);
|
|
92
|
-
}
|
|
93
|
-
// Add prettier-ignore comment
|
|
94
|
-
fixed_lines.push('# prettier-ignore');
|
|
95
|
-
// We'll add the description line later
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
// If we're in description, collect continuation lines
|
|
99
|
-
if (in_description) {
|
|
100
|
-
// Stop if we hit another YAML field
|
|
101
|
-
if (line.match(/^[a-z_-]+:/)) {
|
|
102
|
-
// Done collecting description, write it out
|
|
103
|
-
const full_description = description_parts.join(' ');
|
|
104
|
-
fixed_lines.push(`description: ${full_description}`);
|
|
105
|
-
description_parts = [];
|
|
106
|
-
in_description = false;
|
|
107
|
-
// Add the current line (next field)
|
|
108
|
-
fixed_lines.push(line);
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
// Continuation line - collect it
|
|
112
|
-
const trimmed = line.trim();
|
|
113
|
-
if (trimmed && !trimmed.startsWith('#')) {
|
|
114
|
-
description_parts.push(trimmed);
|
|
115
|
-
}
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
// Regular frontmatter line
|
|
119
|
-
fixed_lines.push(line);
|
|
120
|
-
}
|
|
121
|
-
// If we ended while still in description (at end of frontmatter)
|
|
122
|
-
if (in_description && description_parts.length > 0) {
|
|
123
|
-
const full_description = description_parts.join(' ');
|
|
124
|
-
fixed_lines.push(`description: ${full_description}`);
|
|
125
|
-
}
|
|
126
|
-
return fixed_lines.join('\n');
|
|
127
|
-
}
|
|
128
|
-
//# sourceMappingURL=doctor.js.map
|