Merge pull request #11 from bensabic/feature/dx-workflows

ci(workflows): add skill sync and validation workflows
This commit is contained in:
Corey Haines
2026-01-26 17:27:43 -08:00
committed by GitHub
4 changed files with 306 additions and 25 deletions

184
.github/scripts/sync-skills.js vendored Normal file
View File

@@ -0,0 +1,184 @@
#!/usr/bin/env node
/**
* Sync marketplace.json and README.md with skills directory.
*
* Scans the skills/ directory for valid skills (directories containing SKILL.md)
* and updates marketplace.json and the README skills table to match.
*/
const fs = require("fs");
const path = require("path");
const SKILLS_DIR = "skills";
const MARKETPLACE_FILE = ".claude-plugin/marketplace.json";
const README_FILE = "README.md";
/**
* Parse YAML frontmatter from a SKILL.md file
*/
function parseFrontmatter(content) {
const match = content.match(/^---\n([\s\S]*?)\n---/);
if (!match) return {};
const frontmatter = {};
const lines = match[1].split("\n");
for (const line of lines) {
const colonIndex = line.indexOf(":");
if (colonIndex === -1) continue;
const key = line.slice(0, colonIndex).trim();
let value = line.slice(colonIndex + 1).trim();
// Remove quotes if present
if ((value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))) {
value = value.slice(1, -1);
}
frontmatter[key] = value;
}
return frontmatter;
}
/**
* Get all skills with their metadata
*/
function getSkillsWithMetadata() {
if (!fs.existsSync(SKILLS_DIR)) {
return [];
}
return fs
.readdirSync(SKILLS_DIR, { withFileTypes: true })
.filter((entry) => {
if (!entry.isDirectory()) return false;
const skillFile = path.join(SKILLS_DIR, entry.name, "SKILL.md");
return fs.existsSync(skillFile);
})
.map((entry) => {
const skillFile = path.join(SKILLS_DIR, entry.name, "SKILL.md");
const content = fs.readFileSync(skillFile, "utf8");
const frontmatter = parseFrontmatter(content);
return {
dir: entry.name,
path: `./${SKILLS_DIR}/${entry.name}`,
name: frontmatter.name || entry.name,
description: frontmatter.description || "",
};
})
.sort((a, b) => a.name.localeCompare(b.name));
}
/**
* Update skill count in description
*/
function updateSkillCount(description, count) {
return description.replace(/\d+ marketing skills/, `${count} marketing skills`);
}
/**
* Truncate description to a maximum length
*/
function truncateDescription(description, maxLength = 120) {
if (description.length <= maxLength) return description;
// Find last space before maxLength to avoid cutting words
const truncated = description.slice(0, maxLength);
const lastSpace = truncated.lastIndexOf(" ");
return truncated.slice(0, lastSpace) + "...";
}
/**
* Generate the skills table for README
*/
function generateSkillsTable(skills) {
const header = "| Skill | Description |\n|-------|-------------|";
const rows = skills.map((skill) => {
const link = `[${skill.name}](skills/${skill.dir}/)`;
const description = truncateDescription(skill.description);
return `| ${link} | ${description} |`;
});
return [header, ...rows].join("\n");
}
/**
* Update README.md with new skills table
*/
function updateReadme(skills) {
const content = fs.readFileSync(README_FILE, "utf8");
// Match content between skill list markers
const tableRegex = /(<!-- SKILLS:START -->\n)[\s\S]*?(\n<!-- SKILLS:END -->)/;
const newTable = generateSkillsTable(skills);
if (!tableRegex.test(content)) {
console.log("WARNING: Could not find skill markers in README.md");
return false;
}
const newContent = content.replace(tableRegex, `$1${newTable}$2`);
if (newContent === content) {
return false;
}
fs.writeFileSync(README_FILE, newContent);
return true;
}
/**
* Update marketplace.json with skills list
*/
function updateMarketplace(skills) {
const marketplace = JSON.parse(fs.readFileSync(MARKETPLACE_FILE, "utf8"));
const plugin = marketplace.plugins[0];
const existingSkills = plugin.skills || [];
const currentSkills = skills.map((s) => s.path);
if (JSON.stringify(currentSkills) === JSON.stringify(existingSkills)) {
return { updated: false };
}
plugin.skills = currentSkills;
plugin.description = updateSkillCount(plugin.description, currentSkills.length);
fs.writeFileSync(MARKETPLACE_FILE, JSON.stringify(marketplace, null, 2) + "\n");
const added = currentSkills.filter((s) => !existingSkills.includes(s));
const removed = existingSkills.filter((s) => !currentSkills.includes(s));
return { updated: true, added, removed };
}
function main() {
const skills = getSkillsWithMetadata();
const marketplaceResult = updateMarketplace(skills);
const readmeUpdated = updateReadme(skills);
if (!marketplaceResult.updated && !readmeUpdated) {
console.log("Everything is already in sync");
return;
}
if (marketplaceResult.updated) {
if (marketplaceResult.added.length) {
console.log(`Added: ${marketplaceResult.added.join(", ")}`);
}
if (marketplaceResult.removed.length) {
console.log(`Removed: ${marketplaceResult.removed.join(", ")}`);
}
console.log(`Updated marketplace.json (${skills.length} skills)`);
}
if (readmeUpdated) {
console.log("Updated README.md skills table");
}
}
main();

30
.github/workflows/sync-skills.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: Sync Skills
on:
push:
branches: [main]
paths:
- 'skills/**'
jobs:
sync:
runs-on: ubuntu-slim
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
persist-credentials: true
- name: Sync skills
run: node .github/scripts/sync-skills.js
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v7
with:
commit_user_name: Coreybot
commit_user_email: coreybot+github-actions[bot]@users.noreply.github.com
commit_message: "chore: sync skills with marketplace.json and README"
file_pattern: ".claude-plugin/marketplace.json README.md"

65
.github/workflows/validate-skill.yml vendored Normal file
View File

@@ -0,0 +1,65 @@
name: Validate Agent Skill
on:
push:
branches: [main]
paths:
- "**/SKILL.md"
pull_request:
branches: [main]
paths:
- "**/SKILL.md"
concurrency:
group: validate-skill-${{ github.ref }}
cancel-in-progress: true
jobs:
detect-changes:
runs-on: ubuntu-slim
if: github.event.pull_request.draft != true && github.actor != 'dependabot[bot]'
outputs:
skills: ${{ steps.changed-skills.outputs.skills }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Get changed skills
id: changed-skills
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE=${{ github.event.pull_request.base.sha }}
HEAD=${{ github.event.pull_request.head.sha }}
else
BASE=${{ github.event.before }}
HEAD=${{ github.event.after }}
fi
# Find changed SKILL.md files and extract skill directories
SKILLS=$(git diff --name-only $BASE $HEAD | \
grep 'SKILL.md$' | \
xargs -I {} dirname {} | \
sort -u | \
jq -R -s -c 'split("\n") | map(select(length > 0))')
echo "skills=$SKILLS" >> $GITHUB_OUTPUT
echo "Changed skills: $SKILLS"
validate:
needs: detect-changes
if: needs.detect-changes.outputs.skills != '[]'
runs-on: ubuntu-slim
strategy:
fail-fast: false
matrix:
skill: ${{ fromJson(needs.detect-changes.outputs.skills) }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Validate ${{ matrix.skill }}
uses: Flash-Brew-Digital/validate-skill@v1
with:
path: ${{ matrix.skill }}

View File

@@ -14,31 +14,33 @@ Skills are markdown files that give AI agents specialized knowledge and workflow
## Available Skills ## Available Skills
| Skill | Description | Triggers | <!-- SKILLS:START -->
|-------|-------------|----------| | Skill | Description |
| [ab-test-setup](skills/ab-test-setup/) | Plan and implement A/B tests | "A/B test," "split test," "experiment" | |-------|-------------|
| [analytics-tracking](skills/analytics-tracking/) | Set up tracking and measurement | "tracking," "GA4," "GTM," "events" | | [ab-test-setup](skills/ab-test-setup/) | When the user wants to plan, design, or implement an A/B test or experiment. Also use when the user mentions "A/B... |
| [competitor-alternatives](skills/competitor-alternatives/) | Competitor comparison and alternative pages | "vs page," "alternative page," "[X] vs [Y]" | | [analytics-tracking](skills/analytics-tracking/) | When the user wants to set up, improve, or audit analytics tracking and measurement. Also use when the user mentions... |
| [copy-editing](skills/copy-editing/) | Edit and polish existing copy | "edit this copy," "review my copy," "copy sweep" | | [competitor-alternatives](skills/competitor-alternatives/) | When the user wants to create competitor comparison or alternative pages for SEO and sales enablement. Also use when... |
| [copywriting](skills/copywriting/) | Write or improve marketing copy | "write copy," "rewrite," "headlines," "CTA copy" | | [copy-editing](skills/copy-editing/) | When the user wants to edit, review, or improve existing marketing copy. Also use when the user mentions 'edit this... |
| [email-sequence](skills/email-sequence/) | Build email sequences and drip campaigns | "email sequence," "drip campaign," "nurture" | | [copywriting](skills/copywriting/) | When the user wants to write, rewrite, or improve marketing copy for any page — including homepage, landing pages,... |
| [form-cro](skills/form-cro/) | Optimize lead capture and contact forms | "form optimization," "lead form," "contact form" | | [email-sequence](skills/email-sequence/) | When the user wants to create or optimize an email sequence, drip campaign, automated email flow, or lifecycle email... |
| [free-tool-strategy](skills/free-tool-strategy/) | Plan engineering-as-marketing tools | "free tool," "calculator," "lead gen tool" | | [form-cro](skills/form-cro/) | When the user wants to optimize any form that is NOT signup/registration — including lead capture forms, contact forms,... |
| [launch-strategy](skills/launch-strategy/) | Product launches and feature announcements | "launch," "Product Hunt," "feature release" | | [free-tool-strategy](skills/free-tool-strategy/) | When the user wants to plan, evaluate, or build a free tool for marketing purposes — lead generation, SEO value, or... |
| [marketing-ideas](skills/marketing-ideas/) | 140 SaaS marketing ideas and strategies | "marketing ideas," "growth ideas," "how to market" | | [launch-strategy](skills/launch-strategy/) | When the user wants to plan a product launch, feature announcement, or release strategy. Also use when the user... |
| [marketing-psychology](skills/marketing-psychology/) | 70+ mental models for marketing | "psychology," "mental models," "cognitive bias" | | [marketing-ideas](skills/marketing-ideas/) | When the user needs marketing ideas, inspiration, or strategies for their SaaS or software product. Also use when the... |
| [onboarding-cro](skills/onboarding-cro/) | Improve user activation and onboarding | "onboarding," "activation," "first-run experience" | | [marketing-psychology](skills/marketing-psychology/) | When the user wants to apply psychological principles, mental models, or behavioral science to marketing. Also use when... |
| [page-cro](skills/page-cro/) | Conversion optimization for any marketing page | "optimize [page]," "CRO," "page isn't converting" | | [onboarding-cro](skills/onboarding-cro/) | When the user wants to optimize post-signup onboarding, user activation, first-run experience, or time-to-value. Also... |
| [paid-ads](skills/paid-ads/) | Create and optimize paid ad campaigns | "PPC," "Google Ads," "Meta ads," "paid media" | | [page-cro](skills/page-cro/) | When the user wants to optimize, improve, or increase conversions on any marketing page — including homepage, landing... |
| [paywall-upgrade-cro](skills/paywall-upgrade-cro/) | In-app paywalls and upgrade screens | "paywall," "upgrade screen," "feature gate" | | [paid-ads](skills/paid-ads/) | When the user wants help with paid advertising campaigns on Google Ads, Meta (Facebook/Instagram), LinkedIn, Twitter/X,... |
| [popup-cro](skills/popup-cro/) | Create/optimize popups and modals | "popup," "modal," "exit intent" | | [paywall-upgrade-cro](skills/paywall-upgrade-cro/) | When the user wants to create or optimize in-app paywalls, upgrade screens, upsell modals, or feature gates. Also use... |
| [pricing-strategy](skills/pricing-strategy/) | Design pricing, packaging, and monetization | "pricing," "tiers," "freemium," "willingness to pay" | | [popup-cro](skills/popup-cro/) | When the user wants to create or optimize popups, modals, overlays, slide-ins, or banners for conversion purposes. Also... |
| [programmatic-seo](skills/programmatic-seo/) | Build SEO pages at scale | "programmatic SEO," "template pages," "pages at scale" | | [pricing-strategy](skills/pricing-strategy/) | When the user wants help with pricing decisions, packaging, or monetization strategy. Also use when the user mentions... |
| [referral-program](skills/referral-program/) | Design referral and affiliate programs | "referral," "affiliate," "word of mouth," "viral" | | [programmatic-seo](skills/programmatic-seo/) | When the user wants to create SEO-driven pages at scale using templates and data. Also use when the user mentions... |
| [schema-markup](skills/schema-markup/) | Add structured data and rich snippets | "schema," "JSON-LD," "structured data" | | [referral-program](skills/referral-program/) | When the user wants to create, optimize, or analyze a referral program, affiliate program, or word-of-mouth strategy.... |
| [seo-audit](skills/seo-audit/) | Audit technical and on-page SEO | "SEO audit," "technical SEO," "not ranking" | | [schema-markup](skills/schema-markup/) | When the user wants to add, fix, or optimize schema markup and structured data on their site. Also use when the user... |
| [signup-flow-cro](skills/signup-flow-cro/) | Optimize signup and registration flows | "signup optimization," "registration form" | | [seo-audit](skills/seo-audit/) | When the user wants to audit, review, or diagnose SEO issues on their site. Also use when the user mentions "SEO... |
| [social-content](skills/social-content/) | Create and schedule social media content | "LinkedIn post," "Twitter thread," "social media" | | [signup-flow-cro](skills/signup-flow-cro/) | When the user wants to optimize signup, registration, account creation, or trial activation flows. Also use when the... |
| [social-content](skills/social-content/) | When the user wants help creating, scheduling, or optimizing social media content for LinkedIn, Twitter/X, Instagram,... |
<!-- SKILLS:END -->
## Installation ## Installation