From 52857dfa6194122438e90abdb8074aff649eb985 Mon Sep 17 00:00:00 2001 From: Quanlai Li Date: Wed, 10 Jun 2026 23:44:54 -0700 Subject: [PATCH] ci: validate SKILL.md frontmatter and README skill table (#2) - Add .github/workflows/validate-skills.yml (runs on PRs + pushes to main) - Add validator: every skills/*/SKILL.md needs valid YAML frontmatter with non-empty name + description; README table must match skills/ dirs - Fix geo-query-finder/SKILL.md: add missing frontmatter - Add geo-query-finder + stripe-dispute rows to README table - Remove stale marketing-psychology row (no skill dir) - Bump skill count to 65+ in README and USAGE Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/scripts/validate_skills.py | 83 +++++++++++++++++++++++++++ .github/workflows/validate-skills.yml | 20 +++++++ README.md | 5 +- USAGE.md | 2 +- skills/geo-query-finder/SKILL.md | 10 ++++ 5 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 .github/scripts/validate_skills.py create mode 100644 .github/workflows/validate-skills.yml diff --git a/.github/scripts/validate_skills.py b/.github/scripts/validate_skills.py new file mode 100644 index 0000000..0a11f46 --- /dev/null +++ b/.github/scripts/validate_skills.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +"""Validate skill frontmatter and README skill table. + +Checks: +1. Every skills/*/SKILL.md has a YAML frontmatter block (delimited by ---). +2. The frontmatter parses as valid YAML and has non-empty `name` and `description`. +3. The README skill table lists exactly the skills present in skills/ (count + names). + +Exits non-zero with a report if any check fails. +""" +import pathlib +import re +import sys + +import yaml + +ROOT = pathlib.Path(__file__).resolve().parents[2] +SKILLS_DIR = ROOT / "skills" +README = ROOT / "README.md" + +# Matches README table rows like: | `skill-name` | description | ... | +TABLE_ROW = re.compile(r"^\|\s*`([a-z0-9-]+)`\s*\|") + + +def parse_frontmatter(text: str): + """Return the parsed frontmatter dict, or raise ValueError.""" + if not text.startswith("---"): + raise ValueError("missing YAML frontmatter (file must start with '---')") + parts = text.split("---", 2) + if len(parts) < 3: + raise ValueError("frontmatter block is not closed with a second '---'") + data = yaml.safe_load(parts[1]) + if not isinstance(data, dict): + raise ValueError("frontmatter is not a YAML mapping") + return data + + +def main() -> int: + errors = [] + + skill_dirs = sorted(p.name for p in SKILLS_DIR.iterdir() if p.is_dir()) + + for name in skill_dirs: + skill_md = SKILLS_DIR / name / "SKILL.md" + if not skill_md.exists(): + errors.append(f"{name}: missing SKILL.md") + continue + try: + data = parse_frontmatter(skill_md.read_text(encoding="utf-8")) + except (ValueError, yaml.YAMLError) as exc: + errors.append(f"{name}/SKILL.md: {exc}") + continue + for field in ("name", "description"): + value = data.get(field) + if not (isinstance(value, str) and value.strip()): + errors.append(f"{name}/SKILL.md: missing or empty `{field}` field") + + # Reconcile the README skill table against the skills/ directory. + documented = {m.group(1) for line in README.read_text(encoding="utf-8").splitlines() + if (m := TABLE_ROW.match(line))} + actual = set(skill_dirs) + + undocumented = sorted(actual - documented) + phantom = sorted(documented - actual) + if undocumented: + errors.append("README skill table is missing rows for: " + ", ".join(undocumented)) + if phantom: + errors.append("README skill table has rows with no skills/ dir: " + ", ".join(phantom)) + if len(documented) != len(actual): + errors.append(f"README skill count ({len(documented)}) != skills/ dir count ({len(actual)})") + + if errors: + print("Skill validation FAILED:\n") + for e in errors: + print(f" - {e}") + return 1 + + print(f"All {len(actual)} skills valid; README table matches skills/ directory.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/workflows/validate-skills.yml b/.github/workflows/validate-skills.yml new file mode 100644 index 0000000..c3a2e8c --- /dev/null +++ b/.github/workflows/validate-skills.yml @@ -0,0 +1,20 @@ +name: Validate Skills + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install PyYAML + run: pip install pyyaml + - name: Validate SKILL.md frontmatter and README table + run: python3 .github/scripts/validate_skills.py diff --git a/README.md b/README.md index 83bf541..9affac9 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Most AI marketing tools charge **$50–300/month** for a chat box that gives you **Prerequisites:** [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated. ```bash -# Install all 63+ marketing skills +# Install all 65+ marketing skills npx openclaudia install --all # Or install specific skills @@ -94,6 +94,7 @@ cp -r skills/seo-audit .claude/skills/ # project-level | `schema-markup` | Generate and validate Schema.org structured data | | `programmatic-seo` | Create SEO-optimized pages at scale | | `ahrefs-research` | Ahrefs Python SDK for backlinks, keywords, domain ratings, and traffic | +| `geo-query-finder` | Find which ChatGPT search queries mention a brand (GEO visibility) | ### Content Writing | Skill | Description | @@ -149,7 +150,6 @@ cp -r skills/seo-audit .claude/skills/ # project-level | Skill | Description | |-------|-------------| | `marketing-ideas` | 139 proven marketing ideas by category | -| `marketing-psychology` | 70+ psychological principles for marketing | | `competitor-analysis` | Full competitor strategy breakdown | | `pricing-strategy` | Pricing page and strategy optimization | | `launch-strategy` | Product launch planning and execution | @@ -184,6 +184,7 @@ cp -r skills/seo-audit .claude/skills/ # project-level | `newsletter` | Newsletter growth, engagement, and monetization | | `podcast-marketing` | Podcast production, growth, and promotion | | `podcast-edit` | Edit podcast audio — trim, remove fillers, normalize loudness | +| `stripe-dispute` | Fight Stripe disputes and chargebacks with evidence + counter-dispute | ### CRM & Outreach (API-Powered) | Skill | Description | API Required | diff --git a/USAGE.md b/USAGE.md index 38367de..c859119 100644 --- a/USAGE.md +++ b/USAGE.md @@ -1,6 +1,6 @@ # How to Use OpenClaudia -A practical guide to getting the most out of OpenClaudia's 62+ marketing skills in Claude Code. +A practical guide to getting the most out of OpenClaudia's 65+ marketing skills in Claude Code. --- diff --git a/skills/geo-query-finder/SKILL.md b/skills/geo-query-finder/SKILL.md index 28644d3..c5c1bb1 100644 --- a/skills/geo-query-finder/SKILL.md +++ b/skills/geo-query-finder/SKILL.md @@ -1,3 +1,13 @@ +--- +name: geo-query-finder +description: > + Find which ChatGPT search queries mention a given brand. Tests long-tail + queries against ChatGPT's web-search-enabled model and reports which ones + surface the brand. Use when the user asks to "find queries for [brand]", + "check GEO visibility", "which queries mention [brand]", "geo query finder", + "find AI mentions", or "test ChatGPT queries for [brand]". +--- + # GEO Query Finder Find which ChatGPT search queries mention a given brand. Tests long-tail queries against ChatGPT's web-search-enabled model and reports which ones surface the brand.