From 736bbda78a4ff11abc631be6a3bc8281ccb8bab0 Mon Sep 17 00:00:00 2001 From: francylisboacharuto Date: Thu, 26 Feb 2026 15:52:19 -0300 Subject: [PATCH] feat: Add git-based shared skill registry for team skill management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds skill_registry.py CLI tool with 7 subcommands (init, publish, list, search, install, info, remove) for managing a git-friendly shared skill catalog. No new dependencies — stdlib only. Integrates with existing validate.py and security_scan.py for publish-time checks. Co-Authored-By: Claude Opus 4.6 --- README.md | 653 +++++++++++++++++++++--------------- scripts/skill_registry.py | 686 ++++++++++++++++++++++++++++++++++++++ scripts/validate.py | 28 ++ 3 files changed, 1090 insertions(+), 277 deletions(-) create mode 100644 scripts/skill_registry.py diff --git a/README.md b/README.md index 9150de7..9adbcdc 100644 --- a/README.md +++ b/README.md @@ -1,154 +1,203 @@ -# Agent Skill Creator v4.0 +# Agent Skill Creator -**Create Cross-Platform Agent Skills from Workflow Descriptions** +**Create cross-platform agent skills from natural language workflow descriptions.** [![Agent Skills Open Standard](https://img.shields.io/badge/Agent%20Skills-Open%20Standard-blue)](https://github.com/anthropics/agent-skills-spec) -[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) [![Version](https://img.shields.io/badge/version-4.0.0-brightgreen)]() - -> Works on **8+ platforms**: Claude Code, GitHub Copilot, Cursor, Windsurf, Cline, Codex CLI, Gemini CLI, and any platform supporting the Agent Skills Open Standard. +[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)]() --- -## What It Does +## What Is This? -Agent Skill Creator is a **meta-skill** -- a skill that creates other skills. Describe a repetitive workflow in natural language, and it generates a complete, validated, cross-platform agent skill through an autonomous 5-phase pipeline. - -**Input**: A workflow description like *"Every day I download stock data, analyze trends, and create reports"* +Agent Skill Creator is a **meta-skill** -- a skill that creates other skills. Describe a repetitive workflow in plain English and it generates a complete, validated, cross-platform agent skill through an autonomous 5-phase pipeline. +**Input**: *"Every day I download stock data, analyze trends, and create reports"* **Output**: A ready-to-install skill directory with functional scripts, documentation, cross-platform installer, and spec-compliant SKILL.md. --- ## Quick Start -### Install +### Claude Code ```bash -# Clone and install as a Claude Code skill -git clone https://github.com/user/agent-skill-creator.git -cp -r agent-skill-creator/ ~/.claude/skills/agent-skill-creator/ +git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git ~/.claude/skills/agent-skill-creator ``` -### Use +### GitHub Copilot -Just describe what you need in your agent: - -``` -"Create a skill for analyzing stock market data" - -"Every day I process CSV files manually, automate this" - -"Create a cross-platform skill for weather alerts" - -"Validate this skill for spec compliance" - -"Export this skill for Cursor and Copilot" +```bash +git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .github/skills/agent-skill-creator ``` -The skill creator activates automatically when it detects these patterns and walks through the full pipeline. +### Cursor + +```bash +git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .cursor/rules/agent-skill-creator +``` + +After installing, open your agent and type: + +``` +Create a skill for analyzing CSV files +``` + +The skill creator activates and walks you through the full pipeline. + +For Windsurf, Cline, Codex CLI, Gemini CLI, and other platforms see [Setup by Platform](#setup-by-platform-complete-guide) below. --- -## Supported Platforms +## Usage -Generated skills work on any platform that supports the Agent Skills Open Standard: +### Trigger Phrases -| Platform | Install Location | Notes | -|----------|-----------------|-------| -| **Claude Code** | `~/.claude/skills/` or `.claude/skills/` | Global or per-project | -| **GitHub Copilot** | `.github/skills/` | Repository-level | -| **Cursor** | `.cursor/rules/` | Workspace rules | -| **Windsurf** | `.windsurf/skills/` | Workspace skills | -| **Cline** | `.clinerules/` | Rule-based skills | -| **Codex CLI** | `.codex/skills/` | OpenAI Codex CLI | -| **Gemini CLI** | `.gemini/skills/` | Google Gemini CLI | +Say any of these to your agent: -Each generated skill includes an `install.sh` script that auto-detects your platform and installs to the correct location. +``` +"Create a skill for analyzing stock market data" +"Every day I process CSV files manually, automate this" +"Create a cross-platform skill for weather alerts" +"Automate this workflow" +"I need to automate [repetitive task]" +"Validate this skill" +"Export this skill for Cursor and Copilot" +"Migrate this skill to v4" +``` + +### What Happens + +The creator runs a **5-phase autonomous pipeline**: + +``` +Phase 1: DISCOVERY Research APIs, data sources, and domain knowledge + | +Phase 2: DESIGN Define use cases, methodologies, and outputs + | +Phase 3: ARCHITECTURE Structure skill directory (simple vs. complex suite) + | +Phase 4: DETECTION Generate description + keywords for reliable activation + | +Phase 5: IMPLEMENTATION Create all files, run validation, run security scan +``` + +Output: a complete skill directory you can install on any supported platform. + +--- + +## Setup by Platform (Complete Guide) + +Each platform installs with a single `git clone` directly into the right location. Replace `agent-skill-creator` with the skill name when installing generated skills. + +### Claude Code + +```bash +# Personal skill (available in all projects) +git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git ~/.claude/skills/agent-skill-creator + +# Per-project (scoped to one repo) +git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .claude/skills/agent-skill-creator +``` + +### GitHub Copilot (CLI + VS Code) + +```bash +git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .github/skills/agent-skill-creator +``` + +### Cursor + +```bash +git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .cursor/rules/agent-skill-creator +``` + +Cursor reads SKILL.md natively alongside its `.mdc` rules. + +### Windsurf + +```bash +git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .windsurf/skills/agent-skill-creator +``` + +### Cline + +```bash +git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .clinerules/agent-skill-creator +``` + +### OpenAI Codex CLI + +```bash +git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .codex/skills/agent-skill-creator +``` + +### Gemini CLI + +```bash +git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .gemini/skills/agent-skill-creator +``` + +### Claude Desktop / claude.ai (Export) + +These platforms use `.zip` upload instead of directory copying: + +1. Export: `python3 scripts/export_utils.py ./agent-skill-creator/ --variant desktop` +2. Open Claude Desktop or claude.ai +3. Go to Settings > Skills > Upload skill +4. Select the generated `.zip` file + +### Claude API (Programmatic) + +```bash +python3 scripts/export_utils.py ./agent-skill-creator/ --variant api +``` + +```python +import anthropic + +client = anthropic.Anthropic() + +with open("agent-skill-creator-api-v4.0.0.zip", "rb") as f: + skill = client.skills.create(file=f, name="agent-skill-creator") + +response = client.messages.create( + model="claude-sonnet-4", + messages=[{"role": "user", "content": "Your query here"}], + container={"type": "custom_skill", "skill_id": skill.id}, + betas=["code-execution-2025-08-25", "skills-2025-10-02"], +) +``` + +Note: API sandbox has no network access, no pip install at runtime, and an 8 MB size limit. + +### Updating + +To update an installed skill, just `git pull` from inside the skill directory: + +```bash +cd ~/.claude/skills/agent-skill-creator && git pull +``` --- ## How It Works -The creator runs a **5-phase autonomous pipeline**: - -``` -Phase 1: DISCOVERY Research APIs, data sources, tools, and domain knowledge - | -Phase 2: DESIGN Define use cases, analyses, methodologies, and outputs - | -Phase 3: ARCHITECTURE Structure skill directory (simple skill vs. complex suite) - | -Phase 4: DETECTION Generate description + keywords for reliable activation - | -Phase 5: IMPLEMENTATION Create all files, run validation, run security scan -``` - -### Phase Details - | Phase | What Happens | Key Output | |-------|-------------|------------| -| **Discovery** | Researches the domain, identifies APIs and data sources, maps user needs | Domain model, API list, data sources | +| **Discovery** | Researches the domain, identifies APIs and data sources | Domain model, API list | | **Design** | Defines use cases, analysis methods, output formats | Use case specs, methodology docs | | **Architecture** | Decides simple skill vs. complex suite, plans directory structure | Architecture decision, file plan | -| **Detection** | Crafts SKILL.md description and activation keywords for reliable triggering | SKILL.md frontmatter, trigger phrases | -| **Implementation** | Generates all code, docs, installer; validates and scans for security issues | Complete skill directory | +| **Detection** | Crafts SKILL.md description and activation keywords | SKILL.md frontmatter, trigger phrases | +| **Implementation** | Generates all code, docs, installer; validates and scans | Complete skill directory | For full pipeline documentation, see [references/pipeline-phases.md](references/pipeline-phases.md). --- -## Architecture: Simple Skill vs. Complex Suite - -The creator automatically decides the right architecture based on scope: - -### Simple Skill - -For focused, single-domain tasks (e.g., "analyze CSV files", "extract text from PDFs"). - -``` -stock-analyzer/ - SKILL.md # Under 500 lines, spec-compliant - scripts/ - analyze.py - fetch_data.py - references/ - api-guide.md - assets/ - report-template.html - install.sh - README.md -``` - -### Complex Suite - -For multi-domain workflows requiring coordinated agents (e.g., "full financial analysis pipeline with data collection, analysis, and reporting"). - -``` -financial-analysis-suite/ - SKILL.md # Suite orchestrator, under 500 lines - scripts/ - orchestrator.py - data_collector.py - analyzer.py - report_generator.py - references/ - architecture-guide.md - api-reference.md - assets/ - templates/ - schemas/ - install.sh - README.md -``` - -For detailed architecture guidance, see [references/architecture-guide.md](references/architecture-guide.md). - ---- - ## Generated Skill Format -Every generated skill follows the Agent Skills Open Standard structure: +Every generated skill follows the Agent Skills Open Standard: ``` skill-name/ @@ -160,9 +209,7 @@ skill-name/ README.md # Multi-platform install instructions ``` -### SKILL.md Structure - -The generated SKILL.md includes standard frontmatter: +### SKILL.md Frontmatter ```yaml --- @@ -182,45 +229,26 @@ compatibility: >- Followed by sections: When to Use, Overview, Workflow, Implementation Guidelines, and References. ---- - -## Naming Convention - -Skills follow the **Agent Skills Open Standard** naming rules: - -- **Format**: `kebab-case` (lowercase letters and hyphens only) -- **Length**: 1-64 characters -- **Pattern**: `^[a-z][a-z0-9-]*[a-z0-9]$` -- **No special suffixes** required - -### Examples - -| Good | Bad | -|------|-----| -| `stock-analyzer` | `Stock_Analyzer` | -| `csv-data-cleaner` | `csv_data_cleaner` | -| `financial-analysis-suite` | `FinancialAnalysis` | -| `weather-alerts` | `weather-alerts-cskill` | +**Naming rules**: `kebab-case`, 1-64 characters, pattern `^[a-z][a-z0-9-]*[a-z0-9]$`, must match directory name. --- -## Validation and Security +## Tools ### Validate a Skill -Check that a generated skill is compliant with the Agent Skills Open Standard: +Check spec compliance against the Agent Skills Open Standard: ```bash python3 scripts/validate.py ./my-skill/ + +# JSON output (for CI/CD) +python3 scripts/validate.py ./my-skill/ --json ``` -Validates: -- SKILL.md exists and has valid frontmatter -- Name follows kebab-case convention (1-64 chars) -- Description is under 1024 characters -- SKILL.md is under 500 lines -- Required directory structure is present -- install.sh exists and is executable +**Checks**: SKILL.md existence, valid frontmatter, kebab-case name (1-64 chars), description under 1024 chars, body under 500 lines, required directory structure, install.sh exists and is executable. + +**Exit codes**: `0` = valid (may have warnings), `1` = invalid (errors found). ### Security Scan @@ -228,165 +256,209 @@ Scan for common security issues before sharing or deploying: ```bash python3 scripts/security_scan.py ./my-skill/ + +# JSON output +python3 scripts/security_scan.py ./my-skill/ --json ``` -Detects: -- Hardcoded API keys, tokens, and secrets -- Potential command injection patterns -- Unsafe file operations -- Credential exposure in configuration files +**Detects**: hardcoded API keys (OpenAI, AWS, GitHub, GitLab), tokens and secrets, command injection patterns, unsafe file operations, credential exposure in config files. ---- +**Exit codes**: `0` = clean, `1` = issues found. -## Cross-Platform Export +### Export for Other Platforms -Export skills for different deployment targets: - -### Desktop/Web Export - -Generates a `.zip` archive suitable for sharing or manual installation: +Package skills for distribution: ```bash +# Desktop/Web (.zip for Claude Desktop, claude.ai) python3 scripts/export_utils.py ./my-skill/ --variant desktop + +# API (.zip for Claude API, <=8MB) +python3 scripts/export_utils.py ./my-skill/ --variant api + +# All variants +python3 scripts/export_utils.py ./my-skill/ ``` -### API Export +Output goes to `exports/`. See [references/export-guide.md](references/export-guide.md) for full documentation. -Generates a package suitable for Claude API integration: +### Skill Registry + +Manage a shared skill catalog for teams using a git-based registry: ```bash -python3 scripts/export_utils.py ./my-skill/ --variant api +# Initialize a registry +python3 scripts/skill_registry.py init --registry ./my-registry --name "Team Skills" + +# Publish a skill (validates and security-scans first) +python3 scripts/skill_registry.py publish ./my-skill/ --registry ./my-registry --tags data,csv + +# List all published skills +python3 scripts/skill_registry.py list --registry ./my-registry + +# Search for skills +python3 scripts/skill_registry.py search "finance" --registry ./my-registry + +# Show full details about a skill +python3 scripts/skill_registry.py info stock-analyzer --registry ./my-registry + +# Install a skill for a specific platform +python3 scripts/skill_registry.py install stock-analyzer --registry ./my-registry --platform claude-code + +# Install at project level instead of user level +python3 scripts/skill_registry.py install stock-analyzer --registry ./my-registry --project + +# Remove a skill from the registry +python3 scripts/skill_registry.py remove stock-analyzer --registry ./my-registry --force ``` -For full export documentation, see [references/export-guide.md](references/export-guide.md). +All commands support `--json` for machine-readable output. The registry is a plain directory with `registry.json` and `skills/` — commit it to git for version history, access control via repo permissions, and review workflow via PRs. + +**Exit codes**: `0` = success, `1` = error. --- -## Example Skill +## Architecture Decisions -The repository includes a complete example skill: +The creator automatically decides simple vs. complex based on scope: -### article-to-prototype +| Factor | Simple Skill | Complex Suite | +|--------|-------------|---------------| +| Workflows | 1-2 | 3+ distinct | +| Code size | <1000 lines | >2000 lines | +| Structure | Single SKILL.md | Multiple component SKILL.md files | -Converts academic articles and research papers into functional prototypes. Demonstrates the full skill structure including scripts, references, and cross-platform installer. - -``` -article-to-prototype/ - SKILL.md - scripts/ - article_processor.py - prototype_generator.py - validation_engine.py - references/ - methodology.md - supported-formats.md - assets/ - prototype-templates/ - install.sh - README.md -``` - -See [article-to-prototype/](article-to-prototype/) for the full example. +For detailed decision logic, see [references/architecture-guide.md](references/architecture-guide.md). --- -## Project Structure +## For AI Agents (Machine-Readable Reference) -The agent-skill-creator repository itself: +This section provides structured metadata for AI agents ingesting this README as context. + +### Activation Triggers ``` -agent-skill-creator/ - SKILL.md # Meta-skill definition (this skill's spec) - README.md # This file - MIGRATION.md # v3.x to v4.0 migration guide - .gitignore - scripts/ - validate.py # Spec compliance validator - security_scan.py # Security scanner - export_utils.py # Cross-platform export tool - install-template.sh # Template for generated install.sh - references/ - pipeline-phases.md # Full 5-phase pipeline docs - architecture-guide.md # Simple skill vs. complex suite - cross-platform-guide.md # Platform-specific details - export-guide.md # Export system documentation - phase1-discovery.md # Phase 1 deep dive - phase2-design.md # Phase 2 deep dive - phase3-architecture.md # Phase 3 deep dive - phase4-detection.md # Phase 4 deep dive - phase5-implementation.md # Phase 5 deep dive - phase6-testing.md # Testing guide - quality-standards.md # Quality standards reference - templates-guide.md # Template system guide - templates/ # Skill templates - tools/ # Validation and scanning tools - examples/ # Example configurations - integrations/ - agentdb_bridge.py # AgentDB integration bridge - agentdb_real_integration.py - fallback_system.py # Graceful degradation system - learning_feedback.py # Learning loop integration - validation_system.py # Integration validation - article-to-prototype/ # Example generated skill - exports/ # Export output directory - docs/ # Additional documentation - CHANGELOG.md # Version history - NAMING_CONVENTIONS.md # Naming rules reference - PIPELINE_ARCHITECTURE.md # Pipeline internals - DECISION_LOGIC.md # Architecture decision logic - CLAUDE_SKILLS_ARCHITECTURE.md +create an agent for [objective] +create a skill for [domain] +develop a custom skill +automate this workflow +every day I [task] +I repeatedly need to [process] +I need to automate [task] +create a cross-platform skill for [objective] +validate this skill +check if this skill is spec-compliant +export this skill for [platform] +package this skill for [platform] +migrate this skill to v4 +update this skill to the new standard +create a multi-agent suite for [objective] +create a skill from the [name] template ``` +### Install Commands + +```bash +# Claude Code (personal) +git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git ~/.claude/skills/agent-skill-creator +# GitHub Copilot +git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .github/skills/agent-skill-creator +# Cursor +git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .cursor/rules/agent-skill-creator +# Windsurf +git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .windsurf/skills/agent-skill-creator +# Cline +git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .clinerules/agent-skill-creator +# Codex CLI +git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .codex/skills/agent-skill-creator +# Gemini CLI +git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .gemini/skills/agent-skill-creator +# Update +cd /agent-skill-creator && git pull +``` + +### Tool Commands + +```bash +# Validate +python3 scripts/validate.py PATH # Human output +python3 scripts/validate.py PATH --json # Machine output + +# Security scan +python3 scripts/security_scan.py PATH +python3 scripts/security_scan.py PATH --json + +# Export +python3 scripts/export_utils.py PATH --variant desktop +python3 scripts/export_utils.py PATH --variant api + +# Registry +python3 scripts/skill_registry.py init --registry PATH --name NAME +python3 scripts/skill_registry.py publish SKILL_PATH --registry PATH --tags T1,T2 +python3 scripts/skill_registry.py list --registry PATH [--json] +python3 scripts/skill_registry.py search QUERY --registry PATH [--json] +python3 scripts/skill_registry.py install SKILL_NAME --registry PATH --platform PLATFORM +python3 scripts/skill_registry.py info SKILL_NAME --registry PATH [--json] +python3 scripts/skill_registry.py remove SKILL_NAME --registry PATH --force +``` + +### Platform Paths + +| Platform | Path | Scope | +|----------|------|-------| +| Claude Code | `~/.claude/skills/` | User-level | +| Claude Code | `.claude/skills/` | Project-level | +| GitHub Copilot | `.github/skills/` | Project-level | +| Cursor | `.cursor/rules/` | Workspace | +| Windsurf | `.windsurf/skills/` | Workspace | +| Cline | `.clinerules/` | Workspace | +| Codex CLI | `.codex/skills/` | Workspace | +| Gemini CLI | `.gemini/skills/` | Workspace | +| Claude Desktop | `.zip` upload | App-level | +| claude.ai | `.zip` upload | Web | +| Claude API | `.zip` via API | Programmatic | + +### SKILL.md Spec (Required Fields) + +```yaml --- - -## Activation Triggers - -The skill creator activates when it detects phrases like: - -- "Create an agent for ..." -- "Create a skill for ..." -- "Automate this workflow" -- "Every day I have to ..." -- "I need to automate ..." -- "Create a cross-platform skill for ..." -- "Validate this skill" -- "Export this skill for [platform]" -- "Migrate this skill to v4" - -See [references/phase4-detection.md](references/phase4-detection.md) for the full activation pattern reference. - +name: kebab-case-name # 1-64 chars, ^[a-z][a-z0-9-]*[a-z0-9]$ +description: >- # 1-1024 chars, include activation keywords + What this skill does... +license: MIT +metadata: + author: Author Name + version: X.Y.Z --- +# Body: <500 lines. Move detailed content to references/. +``` -## AgentDB Integration (Optional) +### Pipeline Phases -Skills can optionally integrate with AgentDB for persistent learning across sessions: +``` +DISCOVERY -> DESIGN -> ARCHITECTURE -> DETECTION -> IMPLEMENTATION +``` -- **Learning feedback**: Skills improve based on usage patterns -- **Cross-session memory**: Retain context between conversations -- **Performance metrics**: Track skill effectiveness over time - -AgentDB is not required. Skills work fully without it. See [references/agentdb-integration.md](references/agentdb-integration.md) for setup details. +Each phase is documented in `references/phase{1..5}-*.md`. --- ## Migration from v3.x -If you have skills created with v3.x of agent-skill-creator: +Key changes in v4.0: -**Key changes in v4.0:** - `-cskill` suffix removed from skill names (use standard kebab-case) -- `marketplace.json` simplified (optional for simple skills) - SKILL.md body limited to 500 lines (move detail to `references/`) - `install.sh` cross-platform installer added - Spec validation and security scanning tools added +- `marketplace.json` simplified (optional for simple skills) + +Quick migration: -**Quick migration:** ```bash -# Rename directory (remove -cskill suffix) mv my-skill-cskill/ my-skill/ - -# Update SKILL.md name field -# Validate the migrated skill +# Update SKILL.md name field to remove -cskill suffix python3 scripts/validate.py ./my-skill/ ``` @@ -394,38 +466,65 @@ For the complete migration guide, see [MIGRATION.md](MIGRATION.md). --- -## Advanced Features +## Troubleshooting -### Interactive Mode +**Skill not activating**: Ensure SKILL.md `description` field contains the trigger phrases you expect. The description is the primary activation mechanism. -For complex skills, the creator can run in interactive mode, asking clarifying questions before generating: +**Validation fails on name**: Names must be kebab-case, 1-64 characters, no consecutive hyphens, no leading/trailing hyphens. Pattern: `^[a-z][a-z0-9-]*[a-z0-9]$`. + +**SKILL.md too long**: Body must be under 500 lines. Move detailed documentation to `references/` and link from the main SKILL.md. + +**Export fails with size error**: API exports have an 8 MB limit. Reduce asset sizes or exclude large files. + +**install.sh not executable**: Run `chmod +x install.sh` before executing. + +**Platform not auto-detected**: Use `./install.sh --platform ` to specify explicitly. + +--- + +## Project Structure ``` -"Create a skill for financial analysis" (interactive) +agent-skill-creator/ + SKILL.md # Meta-skill definition + README.md # This file + MIGRATION.md # v3.x to v4.0 migration guide + scripts/ + validate.py # Spec compliance validator + security_scan.py # Security scanner + export_utils.py # Cross-platform export tool + skill_registry.py # Git-based shared skill registry + install-template.sh # Template for generated install.sh + references/ + pipeline-phases.md # Full 5-phase pipeline docs + architecture-guide.md # Simple skill vs. complex suite + cross-platform-guide.md # Platform-specific details + export-guide.md # Export system documentation + phase1-discovery.md # Phase 1 deep dive + phase2-design.md # Phase 2 deep dive + phase3-architecture.md # Phase 3 deep dive + phase4-detection.md # Phase 4 deep dive + phase5-implementation.md # Phase 5 deep dive + phase6-testing.md # Testing guide + quality-standards.md # Quality standards reference + templates-guide.md # Template system guide + templates/ # Skill templates + tools/ # Validation and scanning tools + examples/ # Example configurations + integrations/ + agentdb_bridge.py # AgentDB integration bridge + fallback_system.py # Graceful degradation system + learning_feedback.py # Learning loop integration + validation_system.py # Integration validation + article-to-prototype/ # Example generated skill + exports/ # Export output directory + docs/ + CHANGELOG.md # Version history + NAMING_CONVENTIONS.md # Naming rules reference + PIPELINE_ARCHITECTURE.md # Pipeline internals + DECISION_LOGIC.md # Architecture decision logic ``` -See [references/interactive-mode.md](references/interactive-mode.md). - -### Multi-Agent Suites - -Create coordinated multi-agent systems where specialized agents collaborate: - -``` -"Create a multi-agent suite for end-to-end data pipeline" -``` - -See [references/multi-agent-guide.md](references/multi-agent-guide.md). - -### Template-Based Creation - -Use pre-built templates to accelerate skill creation: - -``` -"Create a skill from the data-analysis template" -``` - -See [references/templates-guide.md](references/templates-guide.md). - --- ## Contributing @@ -441,7 +540,7 @@ See [references/templates-guide.md](references/templates-guide.md). ## License -MIT License. See [LICENSE](LICENSE) for details. +MIT License. --- diff --git a/scripts/skill_registry.py b/scripts/skill_registry.py new file mode 100644 index 0000000..508ee20 --- /dev/null +++ b/scripts/skill_registry.py @@ -0,0 +1,686 @@ +#!/usr/bin/env python3 +""" +Git-Based Shared Skill Registry. + +Manages a git-friendly skill registry for publishing, discovering, and installing +cross-platform agent skills. The registry is a directory with a registry.json +manifest and a skills/ folder — no servers, no databases, no new dependencies. + +Usage: + python3 scripts/skill_registry.py init [--name NAME] [--registry PATH] + python3 scripts/skill_registry.py publish [--registry PATH] [--tags T1,T2] [--force] [--json] + python3 scripts/skill_registry.py list [--registry PATH] [--json] + python3 scripts/skill_registry.py search [--registry PATH] [--json] + python3 scripts/skill_registry.py install [--registry PATH] [--platform PLATFORM] [--project] [--force] [--json] + python3 scripts/skill_registry.py info [--registry PATH] [--json] + python3 scripts/skill_registry.py remove [--registry PATH] [--force] + +Exit codes: + 0 - Success + 1 - Error +""" + +import argparse +import json +import os +import re +import shutil +import sys +from datetime import datetime, timezone +from pathlib import Path +from typing import Optional + +# --- Import sibling scripts --- + +_SCRIPTS_DIR = Path(__file__).resolve().parent +if str(_SCRIPTS_DIR) not in sys.path: + sys.path.insert(0, str(_SCRIPTS_DIR)) + +from validate import validate_skill, _parse_frontmatter, _parse_yaml_field, _parse_subfield_value +from security_scan import security_scan + + +# --- Constants --- + +ALL_PLATFORMS = ["claude-code", "copilot", "cursor", "windsurf", "cline", "codex", "gemini"] + +PLATFORM_PATHS_USER = { + "claude-code": "~/.claude/skills", + "copilot": "~/.copilot/skills", + "cursor": "~/.cursor/rules", + "windsurf": "~/.windsurf/skills", + "cline": "~/.cline/rules", + "codex": "~/.codex/skills", + "gemini": "~/.gemini/skills", +} + +PLATFORM_PATHS_PROJECT = { + "claude-code": ".claude/skills", + "copilot": ".github/skills", + "cursor": ".cursor/rules", + "windsurf": ".windsurf/skills", + "cline": ".clinerules", + "codex": ".codex/skills", + "gemini": ".gemini/skills", +} + +# Directories/files to exclude when copying skills +COPY_IGNORE_PATTERNS = shutil.ignore_patterns( + ".git", "__pycache__", "node_modules", ".venv", "venv", "env", + ".pytest_cache", ".mypy_cache", "dist", "build", "*.pyc", "*.pyo", +) + +# Stop words for auto-tagging +STOP_WORDS = { + "a", "an", "the", "and", "or", "but", "is", "are", "was", "were", "be", + "been", "being", "in", "on", "at", "to", "for", "of", "with", "by", + "from", "as", "into", "through", "during", "before", "after", "above", + "below", "between", "out", "off", "over", "under", "again", "further", + "then", "once", "here", "there", "when", "where", "why", "how", "all", + "each", "every", "both", "few", "more", "most", "other", "some", "such", + "no", "nor", "not", "only", "own", "same", "so", "than", "too", "very", + "can", "will", "just", "should", "now", "it", "its", "this", "that", + "these", "those", "he", "she", "we", "they", "what", "which", "who", + "whom", "do", "does", "did", "has", "have", "had", "having", "using", +} + +MIN_TAG_LENGTH = 3 + + +# --- Registry I/O --- + +def load_registry(registry_path: Path) -> dict: + """Read and parse registry.json from the registry directory.""" + manifest = registry_path / "registry.json" + if not manifest.exists(): + print(f"Error: registry.json not found in {registry_path}", file=sys.stderr) + print("Run 'skill_registry.py init' first.", file=sys.stderr) + sys.exit(1) + try: + return json.loads(manifest.read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError) as exc: + print(f"Error reading registry.json: {exc}", file=sys.stderr) + sys.exit(1) + + +def save_registry(registry_path: Path, data: dict) -> None: + """Atomic write: write to .tmp then rename.""" + manifest = registry_path / "registry.json" + tmp = registry_path / "registry.json.tmp" + try: + tmp.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + tmp.replace(manifest) + except OSError as exc: + # Clean up tmp on failure + if tmp.exists(): + tmp.unlink() + print(f"Error writing registry.json: {exc}", file=sys.stderr) + sys.exit(1) + + +# --- Metadata Extraction --- + +def extract_skill_metadata(skill_path: Path) -> dict: + """ + Parse SKILL.md frontmatter into a metadata dict. + + Returns dict with keys: name, description, version, author, license. + Missing fields default to empty string. + """ + skill_md = skill_path / "SKILL.md" + if not skill_md.exists(): + return {"name": "", "description": "", "version": "", "author": "", "license": ""} + + content = skill_md.read_text(encoding="utf-8") + frontmatter, _ = _parse_frontmatter(content) + if frontmatter is None: + return {"name": "", "description": "", "version": "", "author": "", "license": ""} + + name = _parse_yaml_field(frontmatter, "name") or "" + description = _parse_yaml_field(frontmatter, "description") or "" + license_val = _parse_yaml_field(frontmatter, "license") or "" + + # Version: try metadata.version first, then top-level version + version = _parse_subfield_value(frontmatter, "metadata", "version") + if not version: + version = _parse_yaml_field(frontmatter, "version") or "" + + # Author: try metadata.author first + author = _parse_subfield_value(frontmatter, "metadata", "author") or "" + + return { + "name": name.strip(), + "description": description.strip(), + "version": version.strip(), + "author": author.strip(), + "license": license_val.strip(), + } + + +def auto_extract_tags(description: str) -> list[str]: + """ + Extract keyword tags from a description string. + + Splits on non-alphanumeric characters, filters stop words and short words, + returns up to 10 unique lowercase tags. + """ + if not description: + return [] + words = re.split(r"[^a-zA-Z0-9-]+", description.lower()) + seen: set[str] = set() + tags: list[str] = [] + for word in words: + word = word.strip("-") + if len(word) < MIN_TAG_LENGTH: + continue + if word in STOP_WORDS: + continue + if word not in seen: + seen.add(word) + tags.append(word) + if len(tags) >= 10: + break + return tags + + +# --- Platform Detection --- + +def detect_platform() -> str: + """ + Auto-detect the installed agent platform by checking known directories. + + Returns the platform name or "claude-code" as default. + """ + checks = [ + ("claude-code", "~/.claude"), + ("copilot", "~/.copilot"), + ("cursor", "~/.cursor"), + ("windsurf", "~/.windsurf"), + ("cline", "~/.cline"), + ("codex", "~/.codex"), + ("gemini", "~/.gemini"), + ] + for platform, path in checks: + if Path(path).expanduser().exists(): + return platform + return "claude-code" + + +def resolve_install_path(name: str, platform: str, project: bool) -> Path: + """ + Map platform + scope to the filesystem install path for a skill. + + Args: + name: Skill name (used as subdirectory). + platform: Platform identifier. + project: If True, use project-level path; otherwise user-level. + + Returns: + Absolute path where the skill should be installed. + """ + if project: + base = PLATFORM_PATHS_PROJECT.get(platform) + else: + base = PLATFORM_PATHS_USER.get(platform) + + if base is None: + print(f"Error: unknown platform '{platform}'", file=sys.stderr) + print(f"Supported: {', '.join(ALL_PLATFORMS)}", file=sys.stderr) + sys.exit(1) + + return Path(base).expanduser().resolve() / name + + +# --- Table Formatting --- + +def _format_table(entries: list[dict]) -> str: + """Format skill entries as an aligned text table.""" + if not entries: + return "No skills found." + + headers = ["NAME", "VERSION", "AUTHOR", "TAGS"] + rows = [] + for entry in entries: + tags = ", ".join(entry.get("tags", [])) + rows.append([ + entry.get("name", ""), + entry.get("version", ""), + entry.get("author", ""), + tags, + ]) + + # Calculate column widths + widths = [len(h) for h in headers] + for row in rows: + for i, cell in enumerate(row): + widths[i] = max(widths[i], len(cell)) + + # Build output + lines = [] + header_line = " ".join(h.ljust(widths[i]) for i, h in enumerate(headers)) + lines.append(header_line) + for row in rows: + lines.append(" ".join(cell.ljust(widths[i]) for i, cell in enumerate(row))) + return "\n".join(lines) + + +# --- Subcommands --- + +def cmd_init(args: argparse.Namespace) -> None: + """Initialize a new skill registry.""" + registry_path = Path(args.registry).resolve() + manifest = registry_path / "registry.json" + + if manifest.exists(): + print(f"Error: registry already exists at {registry_path}", file=sys.stderr) + sys.exit(1) + + registry_path.mkdir(parents=True, exist_ok=True) + (registry_path / "skills").mkdir(exist_ok=True) + + name = args.name or "Shared Skills" + data = { + "registry": { + "name": name, + "created": datetime.now(timezone.utc).isoformat(timespec="seconds"), + "schema_version": "1", + }, + "skills": [], + } + save_registry(registry_path, data) + print(f"Registry initialized: {registry_path}") + print(f" Name: {name}") + print(f" Manifest: {manifest}") + print(f" Skills dir: {registry_path / 'skills'}") + + +def cmd_publish(args: argparse.Namespace) -> None: + """Publish a skill to the registry.""" + registry_path = Path(args.registry).resolve() + skill_path = Path(args.skill_path).resolve() + + if not skill_path.is_dir(): + print(f"Error: skill path is not a directory: {skill_path}", file=sys.stderr) + sys.exit(1) + + # Step 1: Validate + validation = validate_skill(str(skill_path)) + if not validation["valid"]: + print("Validation failed:", file=sys.stderr) + for err in validation["errors"]: + print(f" [ERROR] {err}", file=sys.stderr) + sys.exit(1) + + # Step 2: Security scan + scan = security_scan(str(skill_path)) + high_issues = [i for i in scan["issues"] if i["severity"] == "high"] + other_issues = [i for i in scan["issues"] if i["severity"] != "high"] + + if other_issues: + for issue in other_issues: + location = issue["file"] + if issue["line"] > 0: + location += f":{issue['line']}" + print(f" [WARN] {location}: {issue['description']}") + + if high_issues and not args.force: + print("Security scan found high-severity issues:", file=sys.stderr) + for issue in high_issues: + location = issue["file"] + if issue["line"] > 0: + location += f":{issue['line']}" + print(f" [HIGH] {location}: {issue['description']}", file=sys.stderr) + print("Use --force to publish anyway.", file=sys.stderr) + sys.exit(1) + + # Step 3: Extract metadata + metadata = extract_skill_metadata(skill_path) + name = metadata["name"] + version = metadata["version"] or "0.0.0" + + if not name: + print("Error: could not extract skill name from SKILL.md frontmatter", file=sys.stderr) + sys.exit(1) + + # Step 4: Tags + tags = [] + if args.tags: + tags = [t.strip() for t in args.tags.split(",") if t.strip()] + if not tags: + tags = auto_extract_tags(metadata["description"]) + + # Step 5: Check duplicates + data = load_registry(registry_path) + for existing in data["skills"]: + if existing["name"] == name and existing["version"] == version: + if not args.force: + print( + f"Error: skill '{name}' version '{version}' already exists in registry.", + file=sys.stderr, + ) + print("Use --force to overwrite.", file=sys.stderr) + sys.exit(1) + # Remove old entry if forcing + data["skills"] = [s for s in data["skills"] if not (s["name"] == name and s["version"] == version)] + + # Step 6: Copy skill to registry + dest = registry_path / "skills" / name + if dest.exists(): + shutil.rmtree(dest) + shutil.copytree(skill_path, dest, ignore=COPY_IGNORE_PATTERNS) + + # Step 7: Add entry + entry = { + "name": name, + "description": metadata["description"], + "version": version, + "author": metadata["author"], + "license": metadata["license"], + "tags": tags, + "platforms": list(ALL_PLATFORMS), + "published": datetime.now(timezone.utc).isoformat(timespec="seconds"), + "path": f"skills/{name}", + "validation": { + "valid": validation["valid"], + "errors": len(validation["errors"]), + "warnings": len(validation["warnings"]), + }, + "security": { + "clean": scan["clean"], + "issues": len(scan["issues"]), + }, + } + data["skills"].append(entry) + save_registry(registry_path, data) + + if getattr(args, "json", False): + print(json.dumps(entry, indent=2)) + else: + print(f"Published '{name}' v{version} to registry.") + print(f" Path: {dest}") + print(f" Tags: {', '.join(tags)}") + + +def cmd_list(args: argparse.Namespace) -> None: + """List all skills in the registry.""" + registry_path = Path(args.registry).resolve() + data = load_registry(registry_path) + + if getattr(args, "json", False): + print(json.dumps(data["skills"], indent=2)) + return + + print(_format_table(data["skills"])) + + +def cmd_search(args: argparse.Namespace) -> None: + """Search for skills matching a query.""" + registry_path = Path(args.registry).resolve() + data = load_registry(registry_path) + query = args.query.lower() + + matches = [] + for skill in data["skills"]: + searchable = " ".join([ + skill.get("name", ""), + skill.get("description", ""), + skill.get("author", ""), + " ".join(skill.get("tags", [])), + ]).lower() + if query in searchable: + matches.append(skill) + + if getattr(args, "json", False): + print(json.dumps(matches, indent=2)) + return + + if not matches: + print(f"No skills matching '{args.query}'.") + return + + print(f"Skills matching '{args.query}':\n") + print(_format_table(matches)) + + +def cmd_install(args: argparse.Namespace) -> None: + """Install a skill from the registry.""" + registry_path = Path(args.registry).resolve() + data = load_registry(registry_path) + + # Find skill + skill_entry = None + for skill in data["skills"]: + if skill["name"] == args.skill_name: + skill_entry = skill + break + + if skill_entry is None: + print(f"Error: skill '{args.skill_name}' not found in registry.", file=sys.stderr) + sys.exit(1) + + # Resolve platform + platform = args.platform or detect_platform() + if platform not in ALL_PLATFORMS: + print(f"Error: unknown platform '{platform}'", file=sys.stderr) + print(f"Supported: {', '.join(ALL_PLATFORMS)}", file=sys.stderr) + sys.exit(1) + + # Resolve target path + project = getattr(args, "project", False) + target = resolve_install_path(args.skill_name, platform, project) + + # Check if already installed + if target.exists() and not args.force: + print(f"Error: skill already installed at {target}", file=sys.stderr) + print("Use --force to overwrite.", file=sys.stderr) + sys.exit(1) + + # Copy + source = registry_path / skill_entry["path"] + if not source.exists(): + print(f"Error: skill files not found at {source}", file=sys.stderr) + sys.exit(1) + + if target.exists(): + shutil.rmtree(target) + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copytree(source, target, ignore=COPY_IGNORE_PATTERNS) + + if getattr(args, "json", False): + print(json.dumps({ + "installed": True, + "skill": args.skill_name, + "platform": platform, + "path": str(target), + }, indent=2)) + return + + scope = "project" if project else "user" + print(f"Installed '{args.skill_name}' for {platform} ({scope}-level).") + print(f" Path: {target}") + + # Platform-specific activation tips + tips = { + "claude-code": "Skill is auto-loaded. Start a new conversation to activate.", + "copilot": "Skill is auto-loaded by Copilot Chat.", + "cursor": "Skill is loaded alongside .mdc rules.", + "windsurf": "Skill is auto-loaded by Windsurf.", + "cline": "Skill is loaded from .clinerules.", + "codex": "Skill is auto-loaded by Codex CLI.", + "gemini": "Skill is auto-loaded by Gemini CLI.", + } + tip = tips.get(platform) + if tip: + print(f" Tip: {tip}") + + +def cmd_info(args: argparse.Namespace) -> None: + """Show detailed info about a skill.""" + registry_path = Path(args.registry).resolve() + data = load_registry(registry_path) + + skill_entry = None + for skill in data["skills"]: + if skill["name"] == args.skill_name: + skill_entry = skill + break + + if skill_entry is None: + print(f"Error: skill '{args.skill_name}' not found in registry.", file=sys.stderr) + sys.exit(1) + + if getattr(args, "json", False): + print(json.dumps(skill_entry, indent=2)) + return + + print(f"Skill: {skill_entry['name']}") + print(f"{'=' * 50}") + print(f" Version: {skill_entry.get('version', 'N/A')}") + print(f" Author: {skill_entry.get('author', 'N/A')}") + print(f" License: {skill_entry.get('license', 'N/A')}") + print(f" Description: {skill_entry.get('description', 'N/A')}") + print(f" Tags: {', '.join(skill_entry.get('tags', []))}") + print(f" Platforms: {', '.join(skill_entry.get('platforms', []))}") + print(f" Published: {skill_entry.get('published', 'N/A')}") + print(f" Path: {skill_entry.get('path', 'N/A')}") + + validation = skill_entry.get("validation", {}) + if validation: + status = "valid" if validation.get("valid") else "invalid" + print(f" Validation: {status} ({validation.get('errors', 0)} errors, {validation.get('warnings', 0)} warnings)") + + security = skill_entry.get("security", {}) + if security: + status = "clean" if security.get("clean") else f"{security.get('issues', 0)} issues" + print(f" Security: {status}") + + print(f"{'=' * 50}") + + +def cmd_remove(args: argparse.Namespace) -> None: + """Remove a skill from the registry.""" + registry_path = Path(args.registry).resolve() + data = load_registry(registry_path) + + # Find skill + skill_entry = None + for skill in data["skills"]: + if skill["name"] == args.skill_name: + skill_entry = skill + break + + if skill_entry is None: + print(f"Error: skill '{args.skill_name}' not found in registry.", file=sys.stderr) + sys.exit(1) + + if not args.force: + print(f"Remove '{args.skill_name}' from registry? Use --force to confirm.", file=sys.stderr) + sys.exit(1) + + # Remove files + skill_dir = registry_path / skill_entry["path"] + if skill_dir.exists(): + shutil.rmtree(skill_dir) + + # Remove entry + data["skills"] = [s for s in data["skills"] if s["name"] != args.skill_name] + save_registry(registry_path, data) + + print(f"Removed '{args.skill_name}' from registry.") + + +# --- CLI --- + +def _add_registry_arg(parser: argparse.ArgumentParser) -> None: + """Add the --registry argument to a subparser.""" + parser.add_argument( + "--registry", default="./registry", + help="Path to the registry directory (default: ./registry)", + ) + + +def build_parser() -> argparse.ArgumentParser: + """Build the argument parser with all subcommands.""" + parser = argparse.ArgumentParser( + prog="skill_registry", + description="Git-based shared skill registry for cross-platform agent skills.", + ) + subparsers = parser.add_subparsers(dest="command", help="Available commands") + + # init + p_init = subparsers.add_parser("init", help="Initialize a new skill registry") + _add_registry_arg(p_init) + p_init.add_argument("--name", help="Registry name (default: 'Shared Skills')") + + # publish + p_publish = subparsers.add_parser("publish", help="Publish a skill to the registry") + p_publish.add_argument("skill_path", help="Path to the skill directory") + _add_registry_arg(p_publish) + p_publish.add_argument("--tags", help="Comma-separated tags (auto-extracted if omitted)") + p_publish.add_argument("--force", action="store_true", help="Overwrite existing or ignore high-severity issues") + p_publish.add_argument("--json", action="store_true", help="Output as JSON") + + # list + p_list = subparsers.add_parser("list", help="List all skills in the registry") + _add_registry_arg(p_list) + p_list.add_argument("--json", action="store_true", help="Output as JSON") + + # search + p_search = subparsers.add_parser("search", help="Search for skills") + p_search.add_argument("query", help="Search query (matches name, description, author, tags)") + _add_registry_arg(p_search) + p_search.add_argument("--json", action="store_true", help="Output as JSON") + + # install + p_install = subparsers.add_parser("install", help="Install a skill from the registry") + p_install.add_argument("skill_name", help="Name of the skill to install") + _add_registry_arg(p_install) + p_install.add_argument("--platform", choices=ALL_PLATFORMS, help="Target platform (auto-detected if omitted)") + p_install.add_argument("--project", action="store_true", help="Install at project level instead of user level") + p_install.add_argument("--force", action="store_true", help="Overwrite existing installation") + p_install.add_argument("--json", action="store_true", help="Output as JSON") + + # info + p_info = subparsers.add_parser("info", help="Show detailed info about a skill") + p_info.add_argument("skill_name", help="Name of the skill") + _add_registry_arg(p_info) + p_info.add_argument("--json", action="store_true", help="Output as JSON") + + # remove + p_remove = subparsers.add_parser("remove", help="Remove a skill from the registry") + p_remove.add_argument("skill_name", help="Name of the skill to remove") + _add_registry_arg(p_remove) + p_remove.add_argument("--force", action="store_true", help="Confirm removal") + + return parser + + +def main() -> None: + """CLI entry point.""" + parser = build_parser() + args = parser.parse_args() + + if args.command is None: + parser.print_help() + sys.exit(1) + + commands = { + "init": cmd_init, + "publish": cmd_publish, + "list": cmd_list, + "search": cmd_search, + "install": cmd_install, + "info": cmd_info, + "remove": cmd_remove, + } + + cmd_func = commands.get(args.command) + if cmd_func is None: + parser.print_help() + sys.exit(1) + + cmd_func(args) + + +if __name__ == "__main__": + main() diff --git a/scripts/validate.py b/scripts/validate.py index 1d73440..303d6c5 100644 --- a/scripts/validate.py +++ b/scripts/validate.py @@ -147,6 +147,34 @@ def _subfield_exists(frontmatter: str, parent: str, child: str) -> bool: return False +def _parse_subfield_value(frontmatter: str, parent: str, child: str) -> Optional[str]: + """ + Extract a sub-field value from under a parent field in YAML frontmatter. + + Args: + frontmatter: The frontmatter text. + parent: The parent field name (e.g., ``metadata``). + child: The child field name (e.g., ``author``). + + Returns: + The sub-field value as a string, or None if not found. + """ + lines = frontmatter.split("\n") + in_parent = False + for line in lines: + stripped = line.strip() + if stripped.startswith(f"{parent}:"): + in_parent = True + continue + if in_parent: + if line and (line[0] == " " or line[0] == "\t"): + if stripped.startswith(f"{child}:"): + return stripped[len(child) + 1:].strip() + else: + in_parent = False + return None + + def _extract_local_links(body: str) -> list[str]: """ Extract local file paths referenced in markdown links within the body.