From 2e400ac80d0c5344e381b45ad552ae87b56afddb Mon Sep 17 00:00:00 2001 From: Mattias Petersson Date: Thu, 16 Jul 2026 09:39:02 +0200 Subject: [PATCH] fix Claude skill template validation --- scripts/skill-check.ts | 15 +++++++++++++-- test/skill-check.test.ts | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 test/skill-check.test.ts diff --git a/scripts/skill-check.ts b/scripts/skill-check.ts index 9182737ee..850d58845 100644 --- a/scripts/skill-check.ts +++ b/scripts/skill-check.ts @@ -13,6 +13,7 @@ import { discoverTemplates, discoverSkillFiles } from './discover-skills'; import * as fs from 'fs'; import * as path from 'path'; import { execSync } from 'child_process'; +import { claude, getExternalHosts } from '../hosts/index'; const ROOT = path.resolve(import.meta.dir, '..'); const ROOT_REALPATH = fs.realpathSync(ROOT); @@ -64,14 +65,26 @@ for (const file of SKILL_FILES) { console.log('\n Templates:'); const TEMPLATES = discoverTemplates(ROOT); +const CLAUDE_SKIPPED_SKILLS = new Set(claude.generation.skipSkills ?? []); for (const { tmpl, output } of TEMPLATES) { const tmplPath = path.join(ROOT, tmpl); const outPath = path.join(ROOT, output); + const skillDir = path.dirname(tmpl); + const skillName = skillDir === '.' ? null : skillDir.split(path.sep)[0]; if (!fs.existsSync(tmplPath)) { console.log(` \u26a0\ufe0f ${output.padEnd(30)} — no template`); continue; } + if (skillName && CLAUDE_SKIPPED_SKILLS.has(skillName)) { + if (fs.existsSync(outPath)) { + hasErrors = true; + console.log(` \u274c ${output.padEnd(30)} — generated file exists but Claude Code skips this skill`); + } else { + console.log(` \u23ed\ufe0f ${output.padEnd(30)} — intentionally skipped for Claude Code`); + } + continue; + } if (!fs.existsSync(outPath)) { hasErrors = true; console.log(` \u274c ${output.padEnd(30)} — generated file missing! Run: bun run gen:skill-docs`); @@ -90,8 +103,6 @@ for (const file of SKILL_FILES) { // ─── External Host Skills (config-driven) ─────────────────── -import { getExternalHosts } from '../hosts/index'; - for (const hostConfig of getExternalHosts()) { const hostDir = path.join(ROOT, hostConfig.hostSubdir, 'skills'); if (fs.existsSync(hostDir)) { diff --git a/test/skill-check.test.ts b/test/skill-check.test.ts new file mode 100644 index 000000000..f301b7c06 --- /dev/null +++ b/test/skill-check.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, test } from 'bun:test'; +import { spawnSync } from 'child_process'; +import * as path from 'path'; + +const ROOT = path.resolve(import.meta.dir, '..'); + +describe('skill-check template coverage', () => { + test('accepts skills intentionally skipped by the Claude host', () => { + const result = spawnSync('bun', ['run', 'scripts/skill-check.ts'], { + cwd: ROOT, + encoding: 'utf8', + }); + + const claudeTemplateLine = result.stdout + .split('\n') + .find((line) => line.includes('claude/SKILL.md')); + + expect(claudeTemplateLine).toContain('intentionally skipped for Claude Code'); + expect(claudeTemplateLine).not.toContain('generated file missing'); + }); +});