mirror of
https://github.com/OpenClaudia/openclaudia-skills.git
synced 2026-09-19 08:06:49 +08:00
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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())
|
||||
@@ -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
|
||||
@@ -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 |
|
||||
|
||||
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user