2 Commits
Author SHA1 Message Date
Leon Lin b9ca2fd804 Fix design-taste-frontend not loading in Claude Code (#152)
Fix design-taste-frontend not loading in Claude Code (#151)
2026-10-19 07:46:43 +02:00
LeonxlnxandClaude Opus 5.5 9919bcb117 Fix design-taste-frontend not loading: quote the description (#151); check every frontmatter in CI
The description added in 2.1.0 had an unquoted ': ', so strict YAML parsers dropped the skill. It is now a folded block scalar. tests/frontmatter.mjs parses every SKILL.md and agents/openai.yaml strictly and checks names, lengths and manifests; check-skills.yml runs it with the registry test and a script syntax check on every push and PR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 07:40:09 +02:00
6 changed files with 109 additions and 4 deletions

No files matched your search

+2 -2
View File
@@ -9,7 +9,7 @@
{
"name": "taste-skill",
"description": "tasteskill v2: art direction for websites, product pages, dashboards and app screens, with a library of researched directions, real pictures from open collections and a real-browser inspector.",
"version": "2.1.1",
"version": "2.1.2",
"source": "./",
"skills": ["./skills/taste-skill"],
"author": {
@@ -24,7 +24,7 @@
{
"name": "taste-skill-classics",
"description": "The classic taste-skill styles and earlier versions (soft, minimalist, brutalist, gpt-taste, Stitch, v1, v2-experimental). Opt-in: each runs only when called by name.",
"version": "2.1.1",
"version": "2.1.2",
"source": "./",
"skills": [
"./skills/soft-skill",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "taste-skill",
"version": "2.1.1",
"version": "2.1.2",
"description": "tasteskill v2: art direction for websites, product pages, dashboards and app screens, checked in a real browser. Classic styles included, opt-in.",
"author": {
"name": "leonxlnx",
+23
View File
@@ -0,0 +1,23 @@
name: Check skills
on:
push:
branches: [main]
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Every SKILL.md frontmatter parses (as Claude Code and Codex parse it)
run: |
npm install --no-save --no-package-lock yaml@2.6.1
node tests/frontmatter.mjs
- name: Skill registry
run: bash tests/skill_test.sh
- name: Scripts parse
run: for f in skills/taste-skill/scripts/*.mjs skills/taste-skill/scripts/lib/*.mjs skills/taste-skill/assets/*.js; do node --check "$f" || exit 1; done
+5
View File
@@ -4,6 +4,11 @@ All notable changes to taste-skill live here. The repo follows SemVer-ish discip
---
## 2.1.2 - 2026-10-19
- **Fix: `design-taste-frontend` did not load in Claude Code** ([#151](https://github.com/Leonxlnx/taste-skill/issues/151)). The description added in 2.1.0 contained an unquoted `: `, so its YAML frontmatter failed to parse and the skill was silently dropped ("Unknown skill"). It is now a folded block scalar.
- **So it cannot happen again:** `tests/frontmatter.mjs` parses every `SKILL.md` frontmatter (and every `agents/openai.yaml`) with a strict YAML parser, checks names, description length and the plugin manifests, and a GitHub Actions workflow (`check-skills.yml`) runs it, the registry test and a syntax check of the scripts on every push and pull request.
## 2.1.1 - 2026-10-19
One skill to reach for. Installing the repo no longer means an agent might pick an older skill for the job.
+11 -1
View File
@@ -1,6 +1,16 @@
---
name: design-taste-frontend
description: tasteskill v2, the anti-slop frontend skill. Art direction for websites, distilled from a study of 247 curated gallery sites (One Page Love, Awwwards Sites of the Day). Deals researched directions (idea, composition, type, palette, photographs, section blueprints) from a library, enforces a strict no-slop house style with a real-browser inspector, and runs a look-and-fix loop on screenshots. Use this whenever you build, design, redesign, restyle, polish or review a landing page, marketing site, portfolio, product page, one-pager, hero or any public-facing web page, in any stack (HTML/CSS, React, Next.js, Vue, Svelte, Astro, Tailwind) — also when the user says a page looks generic, templated, "AI-made", sloppy, or asks to make it look designed, premium, award-level or less like every other site. Also for app screens: dashboards, admin and analytics views, settings, inboxes and other screens people work in, with their own run and rules.
description: >-
tasteskill v2, the anti-slop frontend skill. Art direction for websites, distilled from a study of
247 curated gallery sites (One Page Love, Awwwards Sites of the Day). Deals researched directions
(idea, composition, type, palette, photographs, section blueprints) from a library, enforces a strict
no-slop house style with a real-browser inspector, and runs a look-and-fix loop on screenshots. Use
this whenever you build, design, redesign, restyle, polish or review a landing page, marketing site,
portfolio, product page, one-pager, hero or any public-facing web page, in any stack (HTML/CSS, React,
Next.js, Vue, Svelte, Astro, Tailwind) — also when the user says a page looks generic, templated,
"AI-made", sloppy, or asks to make it look designed, premium, award-level or less like every other
site. Also for app screens: dashboards, admin and analytics views, settings, inboxes and other screens
people work in, with their own run and rules.
---
# tasteskill v2
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env node
// Every skill must load in every agent. Claude Code and Codex read SKILL.md frontmatter with a strict
// YAML parser and silently drop a skill whose frontmatter fails to parse (an unquoted "word: word" in
// a description is enough), so this check parses each one the same way and fails loudly.
//
// npm install --no-save --no-package-lock yaml@2.6.1 (once; CI does this)
// node tests/frontmatter.mjs
import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
let YAML;
try { YAML = await import('yaml'); } catch {
console.error('This check needs the "yaml" package: npm install --no-save --no-package-lock yaml@2.6.1');
process.exit(2);
}
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
const SKILLS = join(ROOT, 'skills');
const errors = [];
const fail = (where, msg) => errors.push(`${where}: ${msg}`);
const parse = (text, where) => {
const doc = YAML.parseDocument(text, { strict: true, uniqueKeys: true, prettyErrors: true });
for (const e of [...doc.errors, ...doc.warnings]) fail(where, `YAML ${e.name}: ${e.message.split('\n')[0]}`);
return doc.errors.length ? null : doc.toJS();
};
const names = new Map();
for (const dir of readdirSync(SKILLS)) {
const file = join(SKILLS, dir, 'SKILL.md');
if (!statSync(join(SKILLS, dir)).isDirectory() || !existsSync(file)) continue;
const where = `skills/${dir}/SKILL.md`;
const src = readFileSync(file, 'utf8');
const m = src.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
if (!m) { fail(where, 'no frontmatter block (--- ... ---) at the top'); continue; }
const fm = parse(m[1], where);
if (!fm) continue;
if (typeof fm.name !== 'string' || !/^[a-z0-9]+(-[a-z0-9]+)*$/.test(fm.name) || fm.name.length > 64) fail(where, `name must be kebab-case, 64 characters at most (got ${JSON.stringify(fm.name)})`);
if (typeof fm.description !== 'string' || !fm.description.trim()) fail(where, 'description must be a non-empty string');
else if (fm.description.length > 1024) fail(where, `description is ${fm.description.length} characters; agents allow 1024 at most`);
if ('disable-model-invocation' in fm && typeof fm['disable-model-invocation'] !== 'boolean') fail(where, 'disable-model-invocation must be true or false');
if (fm.name) { if (names.has(fm.name)) fail(where, `name "${fm.name}" is also used by ${names.get(fm.name)}`); else names.set(fm.name, where); }
const openai = join(SKILLS, dir, 'agents', 'openai.yaml');
if (existsSync(openai)) {
const o = parse(readFileSync(openai, 'utf8'), `skills/${dir}/agents/openai.yaml`);
if (o && o.policy && 'allow_implicit_invocation' in o.policy && typeof o.policy.allow_implicit_invocation !== 'boolean') fail(`skills/${dir}/agents/openai.yaml`, 'policy.allow_implicit_invocation must be true or false');
}
}
// plugin manifests: valid JSON, and every skill path they list exists
for (const rel of ['.claude-plugin/marketplace.json', '.codex-plugin/plugin.json', '.agents/plugins/marketplace.json']) {
const p = join(ROOT, rel);
if (!existsSync(p)) continue;
let j; try { j = JSON.parse(readFileSync(p, 'utf8')); } catch (e) { fail(rel, 'invalid JSON: ' + e.message); continue; }
for (const plugin of j.plugins || []) for (const s of [].concat(plugin.skills || [])) if (!existsSync(join(ROOT, s, 'SKILL.md'))) fail(rel, `plugin "${plugin.name}" lists ${s}, which has no SKILL.md`);
}
// the default skill's data files
const lib = join(SKILLS, 'taste-skill', 'library');
if (existsSync(lib)) for (const f of readdirSync(lib)) if (f.endsWith('.json')) { try { JSON.parse(readFileSync(join(lib, f), 'utf8')); } catch (e) { fail(`skills/taste-skill/library/${f}`, 'invalid JSON: ' + e.message); } }
if (errors.length) {
console.error(`not ok - ${errors.length} problem(s):`);
for (const e of errors) console.error(' ' + e);
process.exit(1);
}
console.log(`ok - ${names.size} skills load: every frontmatter parses, names are unique, manifests point at real skills`);