Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9ca2fd804 | ||
|
|
9919bcb117 |
No files matched your search
@@ -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,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",
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`);
|
||||
Reference in new issue
Block a user