From f7df8b65e3efdb6fe8ac97c7080e63deac3e05c9 Mon Sep 17 00:00:00 2001 From: David Wright Date: Tue, 22 Sep 2026 13:33:22 +1000 Subject: [PATCH 1/2] Add community skill registry Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/skills/community/README.md | 34 +++ .github/skills/registry.json | 201 ++++++++++++++++++ .github/templates/COMMUNITY_SKILL.template.md | 90 ++++++++ .github/workflows/git-ape-docs-check.yml | 6 +- CONTRIBUTING.md | 59 ++++- scripts/generate-docs.js | 193 ++++++++++++++++- scripts/validate-structure.js | 121 +++++++---- website/docs/authoring/skills.md | 20 +- website/docs/skills/overview.md | 2 + website/docs/skills/registry.md | 48 +++++ 10 files changed, 721 insertions(+), 53 deletions(-) create mode 100644 .github/skills/community/README.md create mode 100644 .github/skills/registry.json create mode 100644 .github/templates/COMMUNITY_SKILL.template.md create mode 100644 website/docs/skills/registry.md diff --git a/.github/skills/community/README.md b/.github/skills/community/README.md new file mode 100644 index 0000000..1a3a7f2 --- /dev/null +++ b/.github/skills/community/README.md @@ -0,0 +1,34 @@ +# Community Skills + +This directory is the **third-party skill registry** for Git-Ape. Anyone can +contribute a skill here via pull request — you do not need to be a +maintainer, and you do not need prior approval to open the PR. + +Community skills are **not** maintained by the Git-Ape team. Each skill must +declare its own author (and, ideally, a source repository) so users know who +to contact and where the canonical version lives. + +## How this differs from `.github/skills//` + +| | First-party (`.github/skills//`) | Community (`.github/skills/community//`) | +|---|---|---| +| Maintained by | Git-Ape team | The contributor / their org | +| Required frontmatter | `name`, `description` | `name`, `description`, **`metadata.author`** | +| Review bar | Maintainer review + full CI (structure, script lint, markdownlint) | **Same** — maintainer review + full CI | +| Listed in | [Skill Registry](https://azure.github.io/git-ape/docs/skills/registry) as `first-party` | [Skill Registry](https://azure.github.io/git-ape/docs/skills/registry) as `community` | + +## Adding a skill here + +See [`CONTRIBUTING.md`](../../../CONTRIBUTING.md#contributing-a-community-skill) +for the full process. In short: + +1. Copy [`.github/templates/COMMUNITY_SKILL.template.md`](../../templates/COMMUNITY_SKILL.template.md) + to `.github/skills/community//SKILL.md`. +2. Fill in `metadata.author` (required) and, if you maintain the skill + elsewhere, `metadata.source`. +3. Open a PR. It goes through the same review and CI checks as any + first-party skill. +4. Once merged, `node scripts/generate-docs.js` picks it up automatically — + it appears in `.github/skills/registry.json` and the + [Skill Registry](https://azure.github.io/git-ape/docs/skills/registry) docs + page with no further registration step. diff --git a/.github/skills/registry.json b/.github/skills/registry.json new file mode 100644 index 0000000..80259e0 --- /dev/null +++ b/.github/skills/registry.json @@ -0,0 +1,201 @@ +{ + "$comment": "AUTO-GENERATED — DO NOT EDIT. Source: .github/skills/. Regenerate with `node scripts/generate-docs.js`.", + "schemaVersion": 1, + "skills": [ + { + "name": "azure-cost-estimator", + "tier": "first-party", + "description": "Estimate monthly costs for Azure resources by querying the Azure Retail Prices API. Parses ARM templates to identify resources, SKUs, and regions, then looks up real retail pricing. Produces a per-resource cost breakdown with monthly totals. Use during template generation or when user asks about costs.", + "path": ".github/skills/azure-cost-estimator", + "docsUrl": "https://azure.github.io/git-ape/docs/skills/azure-cost-estimator", + "userInvocable": true, + "author": "Microsoft", + "license": "MIT", + "version": null, + "maturity": "stable", + "source": null + }, + { + "name": "azure-deployment-preflight", + "tier": "first-party", + "description": "Run preflight validation on ARM templates before deployment. Performs what-if analysis, permission checks, and generates a structured report with resource changes (create/modify/delete). Use before any deployment to preview changes and catch issues early.", + "path": ".github/skills/azure-deployment-preflight", + "docsUrl": "https://azure.github.io/git-ape/docs/skills/azure-deployment-preflight", + "userInvocable": true, + "author": "Microsoft", + "license": "MIT", + "version": null, + "maturity": "stable", + "source": null + }, + { + "name": "azure-drift-detector", + "tier": "first-party", + "description": "Detect configuration drift between deployed Azure resources and stored deployment state. Compare actual Azure configuration against desired state in .azure/deployments/, identify differences, and guide user through reconciliation options. Use when checking for manual changes, policy remediations, or unauthorized modifications.", + "path": ".github/skills/azure-drift-detector", + "docsUrl": "https://azure.github.io/git-ape/docs/skills/azure-drift-detector", + "userInvocable": true, + "author": "Microsoft", + "license": "MIT", + "version": null, + "maturity": "stable", + "source": null + }, + { + "name": "azure-integration-tester", + "tier": "first-party", + "description": "Run post-deployment integration tests for Azure resources. Verify Function Apps, Storage Accounts, Databases, App Services are healthy and accessible. Use after successful Azure deployment.", + "path": ".github/skills/azure-integration-tester", + "docsUrl": "https://azure.github.io/git-ape/docs/skills/azure-integration-tester", + "userInvocable": true, + "author": "Microsoft", + "license": "MIT", + "version": null, + "maturity": "stable", + "source": null + }, + { + "name": "azure-naming-research", + "tier": "first-party", + "description": "Research Azure naming constraints and CAF abbreviations for a given resource type. Use when you need to look up the official CAF slug, naming rules (length, scope, valid characters), and derive validation/cleaning regex patterns for an Azure resource. Triggers on: CAF abbreviation lookup, Azure naming rules research, resource naming constraints.", + "path": ".github/skills/azure-naming-research", + "docsUrl": "https://azure.github.io/git-ape/docs/skills/azure-naming-research", + "userInvocable": true, + "author": "Microsoft", + "license": "MIT", + "version": null, + "maturity": "stable", + "source": null + }, + { + "name": "azure-policy-advisor", + "tier": "first-party", + "description": "Assess ARM template resources for Azure Policy compliance. Analyse the template, query existing subscription assignments via `az policy assignment list`, identify unassigned built-in and custom policies (CIS, NIST, FedRAMP), and emit a two-part report: template-fixable gaps (Part 1) and subscription-level policy assignments (Part 2). USE FOR: recommending Azure Policy assignments for an ARM template, auditing a subscription against CIS/NIST/general best practices, deciding which initiatives to assign at sub or management-group scope, distinguishing template-fixable vs platform-level governance gaps. DO NOT USE FOR: per-resource security configuration assessment (use azure-security-analyzer), RBAC role recommendations (use azure-role-selector), CAF naming abbreviations (use azure-naming-research), or pricing estimates (use azure-cost-estimator). INVOKES: az policy assignment list, az policy set-definition list, microsoft_docs_search, microsoft_docs_fetch.", + "path": ".github/skills/azure-policy-advisor", + "docsUrl": "https://azure.github.io/git-ape/docs/skills/azure-policy-advisor", + "userInvocable": true, + "author": "Microsoft", + "license": "MIT", + "version": null, + "maturity": "stable", + "source": null + }, + { + "name": "azure-resource-availability", + "tier": "first-party", + "description": "Query live Azure APIs to validate resource availability before template generation or deployment. Checks VM SKU restrictions, Kubernetes/runtime version support, API version compatibility, and subscription quota. Use during requirements gathering and preflight to catch deployment failures early.", + "path": ".github/skills/azure-resource-availability", + "docsUrl": "https://azure.github.io/git-ape/docs/skills/azure-resource-availability", + "userInvocable": true, + "author": "Microsoft", + "license": "MIT", + "version": null, + "maturity": "stable", + "source": null + }, + { + "name": "azure-resource-visualizer", + "tier": "first-party", + "description": "Analyze deployed Azure resource groups and generate detailed Mermaid architecture diagrams showing relationships between resources. Use for post-deployment visualization, understanding existing infrastructure, or documenting live Azure environments.", + "path": ".github/skills/azure-resource-visualizer", + "docsUrl": "https://azure.github.io/git-ape/docs/skills/azure-resource-visualizer", + "userInvocable": true, + "author": "Microsoft", + "license": "MIT", + "version": null, + "maturity": "stable", + "source": null + }, + { + "name": "azure-rest-api-reference", + "tier": "first-party", + "description": "Look up Azure REST API and ARM template reference documentation for any resource type. Returns exact property schemas, required fields, valid values, and latest stable API versions. Use BEFORE generating or modifying ARM templates to ensure correctness. No Azure connection required.", + "path": ".github/skills/azure-rest-api-reference", + "docsUrl": "https://azure.github.io/git-ape/docs/skills/azure-rest-api-reference", + "userInvocable": true, + "author": "Microsoft", + "license": "MIT", + "version": null, + "maturity": "stable", + "source": null + }, + { + "name": "azure-role-selector", + "tier": "first-party", + "description": "Recommend least-privilege Azure RBAC roles for deployed resources. Finds minimal built-in roles matching desired permissions or creates custom role definitions. Use during security analysis or when configuring access for service principals and managed identities.", + "path": ".github/skills/azure-role-selector", + "docsUrl": "https://azure.github.io/git-ape/docs/skills/azure-role-selector", + "userInvocable": true, + "author": "Microsoft", + "license": "MIT", + "version": null, + "maturity": "stable", + "source": null + }, + { + "name": "azure-security-analyzer", + "tier": "first-party", + "description": "Analyze Azure resource configurations against security best practices using Azure MCP bestpractices service. Produces per-resource security assessment with severity ratings and recommendations. Use during template generation before deployment confirmation.", + "path": ".github/skills/azure-security-analyzer", + "docsUrl": "https://azure.github.io/git-ape/docs/skills/azure-security-analyzer", + "userInvocable": true, + "author": "Microsoft", + "license": "MIT", + "version": null, + "maturity": "stable", + "source": null + }, + { + "name": "azure-stack-deploy", + "tier": "first-party", + "description": "Run an Azure Deployment Stack create (subscription scope) for a prepared Git-Ape deployment artifact and write state.json (schemaVersion 1.0). Use locally so the result matches the CI deploy workflow.", + "path": ".github/skills/azure-stack-deploy", + "docsUrl": "https://azure.github.io/git-ape/docs/skills/azure-stack-deploy", + "userInvocable": true, + "author": "Microsoft", + "license": "MIT", + "version": null, + "maturity": "stable", + "source": null + }, + { + "name": "azure-stack-destroy", + "tier": "first-party", + "description": "Tear down a Git-Ape deployment by ID. Reads `state.json` under `.azure/deployments//` to delete the Azure Deployment Stack and purge soft-deleted Key Vault / Cognitive Services. Refuses to run without `state.json`. Use for any local CLI or VS Code Git-Ape teardown so the result matches the CI destroy workflow.", + "path": ".github/skills/azure-stack-destroy", + "docsUrl": "https://azure.github.io/git-ape/docs/skills/azure-stack-destroy", + "userInvocable": true, + "author": "Microsoft", + "license": "MIT", + "version": null, + "maturity": "stable", + "source": null + }, + { + "name": "git-ape-onboarding", + "tier": "first-party", + "description": "Bootstrap a GitHub repository for Git-Ape CI/CD: Entra app registration, OIDC federated credentials, RBAC role assignments, GitHub environments (azure-deploy/azure-destroy), required secrets, and scaffold Actions workflow files — plus enterprise-wide distribution via a `.github-private` repo (managed-settings.json plugin standards + custom agents). USE FOR: first-time Git-Ape setup, new subscription onboarding, multi-environment (dev/staging/prod) setup, configure OIDC, federated credentials, RBAC setup, GitHub environments, scaffold workflow files, rolling Git-Ape out org/enterprise-wide. DO NOT USE FOR: deploying resources (use git-ape), drift detection alone, secret rotation.", + "path": ".github/skills/git-ape-onboarding", + "docsUrl": "https://azure.github.io/git-ape/docs/skills/git-ape-onboarding", + "userInvocable": true, + "author": "Microsoft", + "license": "MIT", + "version": null, + "maturity": "stable", + "source": null + }, + { + "name": "prereq-check", + "tier": "first-party", + "description": "Validate Git-Ape CLI tool installation (az, gh, jq, git), versions, and auth sessions. Shows platform-specific install commands for anything missing. USE FOR: check Git-Ape prerequisites, what do I need to install for Git-Ape, verify Git-Ape CLI tools, az: command not found, gh: command not found, jq: command not found, git: command not found, az missing, gh missing, jq missing, git missing, fresh machine setup for Git-Ape, dev container setup for Git-Ape, before running git-ape-onboarding, az login required, gh auth login, auth expired, not logged in, outdated az version, minimum az version, upgrade az. DO NOT USE FOR: Anything else. This skill is narrowly scoped to prerequisites checks for Git-Ape's CLI tools and auth sessions. Do not use it for any other purpose.", + "path": ".github/skills/prereq-check", + "docsUrl": "https://azure.github.io/git-ape/docs/skills/prereq-check", + "userInvocable": true, + "author": "Git-Ape", + "license": "MIT", + "version": "0.1.0", + "maturity": "stable", + "source": null + } + ] +} diff --git a/.github/templates/COMMUNITY_SKILL.template.md b/.github/templates/COMMUNITY_SKILL.template.md new file mode 100644 index 0000000..e26e1bc --- /dev/null +++ b/.github/templates/COMMUNITY_SKILL.template.md @@ -0,0 +1,90 @@ + + +--- +title: "Community skill scaffold template" +description: "Copy this file to .github/skills/community//SKILL.md and replace every marker." +--- + + marker in the body. This skill goes through + the same PR review and CI checks (structure validation, markdownlint, + script lint if it has scripts) as a first-party skill. + + Required frontmatter for the real SKILL.md (replace the title/description + block above): + + --- + name: + description: "One sentence describing what the skill does and when it fires. USE FOR: . DO NOT USE FOR: ." + license: MIT + metadata: + author: + source: + maturity: experimental + version: "1.0.0" + --- +--> + +# + +> + +## When to Use + + + +* +* + +## Procedure + + + +### 1. + + + +### 2. + + + +### 3. + + + +## Outputs + + + +## Constraints + +**Always:** + +* + +**Never:** + +* + +## Attribution + +* **Author:** +* **Source:** +* **Support:** + + diff --git a/.github/workflows/git-ape-docs-check.yml b/.github/workflows/git-ape-docs-check.yml index 292e588..fae81ff 100644 --- a/.github/workflows/git-ape-docs-check.yml +++ b/.github/workflows/git-ape-docs-check.yml @@ -40,7 +40,7 @@ jobs: - name: Check for stale docs id: diff run: | - if git diff --quiet website/docs/; then + if git diff --quiet website/docs/ .github/skills/registry.json; then echo "stale=false" >> "$GITHUB_OUTPUT" echo "✅ Generated docs are up to date" else @@ -48,7 +48,7 @@ jobs: echo "⚠️ Generated docs are stale" echo "" echo "Changed files:" - git diff --name-only website/docs/ + git diff --name-only website/docs/ .github/skills/registry.json fi - name: Comment on PR if stale @@ -57,7 +57,7 @@ jobs: with: script: | const { execSync } = require('child_process'); - const changedFiles = execSync('git diff --name-only website/docs/') + const changedFiles = execSync('git diff --name-only website/docs/ .github/skills/registry.json') .toString() .trim() .split('\n') diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 462fe1b..14527fa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additio ## Contribution Model -- **Skills** are community-contributable via Pull Request. +- **Skills** are community-contributable via Pull Request, either as first-party skills (`.github/skills//`) or as third-party **community skills** (`.github/skills/community//`) — see [Contributing a Community Skill](#contributing-a-community-skill). - **Agents** are maintainer-curated. To propose agent changes, open a Discussion first. ## Adding a New Skill @@ -76,6 +76,62 @@ Brief overview of the skill. 3. Step three ``` +## Contributing a Community Skill + +Git-Ape maintains a **skill registry** so third parties can build and ship +skills within the Git-Ape framework without needing to be a maintainer. +Community skills live in their own subdirectory and are picked up +automatically by the generated registry (`.github/skills/registry.json` +and the [Skill Registry](https://azure.github.io/git-ape/docs/skills/registry) +docs page) — no separate registration step is required. + +### Where they live + +``` +.github/skills/community/ +└── your-skill-name/ + └── SKILL.md +``` + +Same rules as first-party skills apply: kebab-case directory name, `name:` +frontmatter matching the directory exactly, and the required +`## When to Use` / `## Procedure` sections. + +### Additional required frontmatter + +Community skills must also set `metadata.author` so users know who built and +maintains the skill: + +```yaml +--- +name: your-skill-name +description: "Short description of what this skill does." +license: MIT +metadata: + author: your-github-handle # Required. Who maintains this skill. + source: https://github.com/you/your-repo # Optional. Canonical home, if not here. + maturity: experimental # Optional. experimental (default) | stable + version: "1.0.0" # Optional. +--- +``` + +Start from [`.github/templates/COMMUNITY_SKILL.template.md`](.github/templates/COMMUNITY_SKILL.template.md) +rather than the first-party `SKILL.template.md` — it includes the required +`metadata.author` field and an `## Attribution` section. + +### Review bar + +Community skills go through **the same process as first-party skills**: +maintainer review plus the full PR validation suite (`validate-structure.js`, +markdownlint, and Script Lint if the skill ships shell/PowerShell scripts). +The only functional differences are the directory location and the required +`metadata.author` field, which CI enforces — a community `SKILL.md` missing +`metadata.author` fails structural validation. + +Community skills are clearly labeled as **third-party, not maintained by the +Git-Ape team** wherever they're listed (registry, docs pages), so users can +make an informed choice about trusting them. + ## Proposing Agent Changes Agents are **maintainer-curated** and not open for direct community contribution via PR. @@ -126,6 +182,7 @@ site. Decision rationale for the harness choice lives in 4. **Submit a PR** — Fill in the PR template and describe your changes. 5. **CI checks run automatically** — The PR validation workflow verifies: - YAML frontmatter has required fields (`name`, `description` for skills; `description` for agents) + - Community skills (`.github/skills/community//`) additionally require `metadata.author` - Skill `name` matches its parent directory name - All skill/agent directories use kebab-case - Every skill directory contains a `SKILL.md` file diff --git a/scripts/generate-docs.js b/scripts/generate-docs.js index 025e726..52c4cab 100644 --- a/scripts/generate-docs.js +++ b/scripts/generate-docs.js @@ -246,7 +246,7 @@ function generateSkillDocs() { ensureDir(outDir); const skillDirs = fs.readdirSync(SKILLS_DIR).filter((d) => { - return fs.statSync(path.join(SKILLS_DIR, d)).isDirectory() && !d.startsWith('aws-'); + return fs.statSync(path.join(SKILLS_DIR, d)).isDirectory() && !d.startsWith('aws-') && d !== 'community'; }); const skills = []; @@ -274,7 +274,16 @@ function generateSkillDocs() { else if (postDeploySkills.includes(name)) phase = 'Post-Deploy'; else if (opsSkills.includes(name)) phase = 'Operations'; - skills.push({ name, slug, description, userInvocable, phase, dir }); + const meta = fm.metadata || {}; + skills.push({ + name, slug, description, userInvocable, phase, dir, + tier: 'first-party', + author: meta.author || 'Microsoft', + license: fm.license || 'MIT', + version: meta.version || null, + maturity: meta.maturity || 'stable', + source: meta.source || null, + }); const bodyTrimmed = rewriteSkillRefLinks(rewriteSkillLinks(body.trim(), dir), './'); @@ -305,8 +314,90 @@ ${bodyTrimmed} writeAutoGenerated(path.join(outDir, `${slug}.md`), `.github/skills/${dir}/SKILL.md`, content); } + // ------------------------------------------------------------------------- + // Community (third-party) skills — .github/skills/community//SKILL.md + // ------------------------------------------------------------------------- + const communityDir = path.join(SKILLS_DIR, 'community'); + const communityOutDir = path.join(outDir, 'community'); + + if (fs.existsSync(communityDir)) { + ensureDir(communityOutDir); + const communityDirs = fs.readdirSync(communityDir).filter((d) => + fs.statSync(path.join(communityDir, d)).isDirectory() + ); + + for (const dir of communityDirs) { + const skillMdPath = path.join(communityDir, dir, 'SKILL.md'); + if (!fs.existsSync(skillMdPath)) continue; + + const raw = readFile(skillMdPath); + const { data: fm, content: body } = matter(raw); + + const slug = slugify(fm.name || dir); + const name = fm.name || dir; + const description = fm.description || ''; + const userInvocable = fm['user-invocable'] !== false; + const argumentHint = fm['argument-hint'] || ''; + const meta = fm.metadata || {}; + const author = meta.author || 'Unknown'; + const maturity = meta.maturity || 'experimental'; + + const entry = { + name, slug, description, userInvocable, phase: 'Community', dir: `community/${dir}`, + tier: 'community', + author, + license: fm.license || null, + version: meta.version || null, + maturity, + source: meta.source || null, + }; + skills.push(entry); + + const bodyTrimmed = rewriteSkillRefLinks(rewriteSkillLinks(body.trim(), `community/${dir}`), './'); + + const content = `--- +title: "${toTitleCase(name)}" +sidebar_label: "${toTitleCase(name)}" +description: "${description.replace(/"/g, '\\"')}" +--- + +# ${toTitleCase(name)} + +> ${description} + +:::info[Third-party community skill] +This skill is contributed and maintained by the community, not by the Git-Ape maintainers. See the [Skill Registry](../registry) for provenance details. +::: + +## Details + +| Property | Value | +|----------|-------| +| **Skill Directory** | \`.github/skills/community/${dir}/\` | +| **Author** | ${author} | +| **Maturity** | ${maturity} | +${entry.source ? `| **Source** | [${entry.source}](${entry.source}) |\n` : ''}| **User Invocable** | ${userInvocable ? '✅ Yes' : '❌ No'} | +${argumentHint ? `| **Usage** | \`/${name} ${argumentHint}\` |\n` : `| **Usage** | \`/${name}\` |\n`} + +## Documentation + +${bodyTrimmed} +`; + + writeAutoGenerated(path.join(communityOutDir, `${slug}.md`), `.github/skills/community/${dir}/SKILL.md`, content); + } + + const communityCatFile = path.join(communityOutDir, '_category_.json'); + fs.writeFileSync(communityCatFile, JSON.stringify({ + label: 'Community Skills', + position: 99, + link: { type: 'generated-index', description: 'Third-party skills contributed by the community.' }, + }, null, 2) + '\n', 'utf8'); + console.log(` ✓ ${path.relative(ROOT, communityCatFile)}`); + } + // Generate overview page - const phases = ['Pre-Deploy', 'Post-Deploy', 'Operations', 'General']; + const phases = ['Pre-Deploy', 'Post-Deploy', 'Operations', 'General', 'Community']; let overviewContent = `--- title: "Skills Overview" sidebar_label: "Overview" @@ -318,12 +409,23 @@ description: "Overview of all Git-Ape skills organized by deployment phase" Skills are focused capabilities invoked by agents at specific stages of the deployment workflow. Each skill handles one task. +> 📇 See the [Skill Registry](./registry) for the full machine-readable catalog (first-party + community) with author and maturity metadata. + `; for (const phase of phases) { const phaseSkills = skills.filter((s) => s.phase === phase); if (phaseSkills.length === 0) continue; + if (phase === 'Community') { + overviewContent += `## Community Skills\n\nThird-party skills contributed under \`.github/skills/community/\`. These are **not** maintained by the Git-Ape maintainers — see each skill's Author for provenance.\n\n| Skill | Description | Author | Maturity | Invocable |\n|-------|-------------|--------|----------|:---------:|\n`; + for (const s of phaseSkills) { + overviewContent += `| [${toTitleCase(s.name)}](./community/${s.slug}) | ${s.description} | ${s.author} | ${s.maturity} | ${s.userInvocable ? '✅' : '❌'} |\n`; + } + overviewContent += '\n'; + continue; + } + overviewContent += `## ${phase} Skills\n\n| Skill | Description | Invocable |\n|-------|-------------|:---------:|\n`; for (const s of phaseSkills) { overviewContent += `| [${toTitleCase(s.name)}](./${s.slug}) | ${s.description} | ${s.userInvocable ? '✅' : '❌'} |\n`; @@ -359,6 +461,87 @@ graph LR return skills; } +// --------------------------------------------------------------------------- +// Skill registry generation (registry.json + docs/skills/registry.md) +// --------------------------------------------------------------------------- +// +// Builds a single machine-readable catalog of every skill (first-party and +// community) so third parties and tooling can discover what's available +// without walking the filesystem. No timestamps are embedded so the output +// stays idempotent for the docs-staleness CI check. +function generateSkillRegistry(skills) { + console.log('\n📇 Generating skill registry...'); + + const registry = { + $comment: 'AUTO-GENERATED — DO NOT EDIT. Source: .github/skills/. Regenerate with `node scripts/generate-docs.js`.', + schemaVersion: 1, + skills: skills + .slice() + .sort((a, b) => a.name.localeCompare(b.name)) + .map((s) => ({ + name: s.name, + tier: s.tier, + description: s.description, + path: `.github/skills/${s.dir}`, + docsUrl: s.tier === 'community' + ? `https://azure.github.io/git-ape/docs/skills/community/${s.slug}` + : `https://azure.github.io/git-ape/docs/skills/${s.slug}`, + userInvocable: s.userInvocable, + author: s.author, + license: s.license, + version: s.version, + maturity: s.maturity, + source: s.source, + })), + }; + + const registryPath = path.join(SKILLS_DIR, 'registry.json'); + fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2) + '\n', 'utf8'); + console.log(` ✓ ${path.relative(ROOT, registryPath)}`); + + const firstParty = registry.skills.filter((s) => s.tier === 'first-party'); + const community = registry.skills.filter((s) => s.tier === 'community'); + + const renderRow = (s) => `| [${toTitleCase(s.name)}](${s.tier === 'community' ? `./community/${s.slug || slugify(s.name)}` : `./${s.slug || slugify(s.name)}`}) | ${s.tier} | ${s.author} | ${s.maturity} | ${s.description} |`; + + const content = `--- +title: "Skill Registry" +sidebar_label: "Registry" +sidebar_position: 2 +description: "Machine-readable catalog of every Git-Ape skill, first-party and community" +--- + +# Skill Registry + +A single catalog of every skill known to Git-Ape — first-party skills maintained in this repository and third-party skills contributed by the community under \`.github/skills/community/\`. + +The raw, machine-readable version lives at [\`.github/skills/registry.json\`](https://github.com/Azure/git-ape/blob/main/.github/skills/registry.json) and is regenerated by \`node scripts/generate-docs.js\` on every change under \`.github/skills/\`. + +## First-Party Skills (${firstParty.length}) + +| Skill | Tier | Author | Maturity | Description | +|-------|------|--------|----------|-------------| +${firstParty.map(renderRow).join('\n')} + +## Community Skills (${community.length}) + +Third-party skills. **Not** maintained by the Git-Ape maintainers — check the Author column for who to contact, and review the skill's source before relying on it. + +${community.length > 0 + ? `| Skill | Tier | Author | Maturity | Description |\n|-------|------|--------|----------|-------------|\n${community.map(renderRow).join('\n')}` + : '_No community skills have been contributed yet. See [Contributing a Community Skill](https://github.com/Azure/git-ape/blob/main/CONTRIBUTING.md#contributing-a-community-skill) to add the first one._'} + +## Contributing to the Registry + +- **First-party skills** live under \`.github/skills//SKILL.md\` and are maintained by the Git-Ape team. +- **Community skills** live under \`.github/skills/community//SKILL.md\` and are contributed by third parties via pull request, following the same [contribution guidelines](https://github.com/Azure/git-ape/blob/main/CONTRIBUTING.md#contributing-a-community-skill) and CI checks as first-party skills. + +Both tiers are picked up automatically — no manual registration step is required beyond adding the \`SKILL.md\` file with valid frontmatter. +`; + + writeAutoGenerated(path.join(DOCS_OUT, 'skills', 'registry.md'), '.github/skills/', content); +} + // --------------------------------------------------------------------------- // Workflow doc generation // --------------------------------------------------------------------------- @@ -592,7 +775,6 @@ The plugin manifest defines the Git-Ape plugin metadata. The same manifest is co | **License** | ${pluginJson.license || 'N/A'} | | **Agents Path** | \`${pluginJson.agents}\` | | **Skills Path** | \`${pluginJson.skills}\` | - ## Keywords ${(pluginJson.keywords || []).map((k) => `\`${k}\``).join(' · ')} @@ -685,12 +867,13 @@ function main() { generateCategoryFiles(); const agents = generateAgentDocs(); const skills = generateSkillDocs(); + generateSkillRegistry(skills); const workflows = generateWorkflowDocs(); generateReferenceDocs(); console.log('\n✅ Documentation generation complete!'); console.log(` Agents: ${agents.length}`); - console.log(` Skills: ${skills.length}`); + console.log(` Skills: ${skills.length} (${skills.filter((s) => s.tier === 'community').length} community)`); console.log(` Workflows: ${workflows.length}`); } diff --git a/scripts/validate-structure.js b/scripts/validate-structure.js index 3549ad9..f6bccbe 100644 --- a/scripts/validate-structure.js +++ b/scripts/validate-structure.js @@ -28,6 +28,10 @@ const SKILLS_DIR = path.join(ROOT, '.github', 'skills'); const KEBAB_CASE_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/; +// Third-party/community skills live in this fixed subdirectory of SKILLS_DIR. +// It is a container, not a skill itself, and is excluded from first-party checks. +const COMMUNITY_DIR_NAME = 'community'; + let errors = []; let warnings = []; @@ -49,11 +53,40 @@ function ok(msg) { // Helpers // --------------------------------------------------------------------------- -function getSkillDirs() { +// Returns one entry per skill directory across both tiers: +// - first-party: direct children of .github/skills/ +// - community: children of .github/skills/community/ (third-party contributions) +// `relDir` is the path relative to SKILLS_DIR (e.g. 'azure-cost-estimator' or +// 'community/foo'); `leaf` is the directory's own name, which must match the +// skill's frontmatter `name` regardless of tier. +function getSkillEntries() { if (!fs.existsSync(SKILLS_DIR)) return []; - return fs.readdirSync(SKILLS_DIR).filter((d) => { - return fs.statSync(path.join(SKILLS_DIR, d)).isDirectory(); - }); + const entries = []; + + const topLevel = fs.readdirSync(SKILLS_DIR).filter((d) => + fs.statSync(path.join(SKILLS_DIR, d)).isDirectory() + ); + + for (const dir of topLevel) { + if (dir === COMMUNITY_DIR_NAME) continue; // container, not a skill + entries.push({ relDir: dir, leaf: dir, tier: 'first-party' }); + } + + const communityRoot = path.join(SKILLS_DIR, COMMUNITY_DIR_NAME); + if (fs.existsSync(communityRoot)) { + const communityDirs = fs.readdirSync(communityRoot).filter((d) => + fs.statSync(path.join(communityRoot, d)).isDirectory() + ); + for (const dir of communityDirs) { + entries.push({ relDir: path.posix.join(COMMUNITY_DIR_NAME, dir), leaf: dir, tier: 'community' }); + } + } + + return entries; +} + +function skillMdPathFor(entry) { + return path.join(SKILLS_DIR, entry.relDir, 'SKILL.md'); } function getAgentFiles() { @@ -108,44 +141,45 @@ function checkKebabCase(dirs, label) { } } -function checkSkillPresence(skillDirs) { +function checkSkillPresence(skillEntries) { console.log('\n📄 SKILL.md presence:'); - for (const dir of skillDirs) { - const skillMd = path.join(SKILLS_DIR, dir, 'SKILL.md'); - if (!fs.existsSync(skillMd)) { - error(`Skill directory '${dir}' is missing SKILL.md`); + for (const entry of skillEntries) { + if (!fs.existsSync(skillMdPathFor(entry))) { + error(`Skill directory '${entry.relDir}' is missing SKILL.md`); } } - const allPresent = skillDirs.every((d) => - fs.existsSync(path.join(SKILLS_DIR, d, 'SKILL.md')) - ); + const allPresent = skillEntries.every((e) => fs.existsSync(skillMdPathFor(e))); if (allPresent) { - ok(`All ${skillDirs.length} skill directories contain SKILL.md`); + ok(`All ${skillEntries.length} skill directories contain SKILL.md`); } } -function checkSkillFrontmatter(skillDirs) { +function checkSkillFrontmatter(skillEntries) { console.log('\n🏷️ Skill frontmatter validation:'); - for (const dir of skillDirs) { - const skillMd = path.join(SKILLS_DIR, dir, 'SKILL.md'); + for (const entry of skillEntries) { + const skillMd = skillMdPathFor(entry); if (!fs.existsSync(skillMd)) continue; const parsed = parseFrontmatter(skillMd); if (!parsed) { - error(`${dir}/SKILL.md: Could not parse YAML frontmatter`); + error(`${entry.relDir}/SKILL.md: Could not parse YAML frontmatter`); continue; } const { data: fm } = parsed; if (!fm.name) { - error(`${dir}/SKILL.md: Missing required frontmatter field 'name'`); - } else if (fm.name !== dir) { - error(`${dir}/SKILL.md: Frontmatter 'name' is '${fm.name}' but directory is '${dir}'`); + error(`${entry.relDir}/SKILL.md: Missing required frontmatter field 'name'`); + } else if (fm.name !== entry.leaf) { + error(`${entry.relDir}/SKILL.md: Frontmatter 'name' is '${fm.name}' but directory is '${entry.leaf}'`); } if (!fm.description) { - error(`${dir}/SKILL.md: Missing required frontmatter field 'description'`); + error(`${entry.relDir}/SKILL.md: Missing required frontmatter field 'description'`); + } + + if (entry.tier === 'community' && !(fm.metadata && fm.metadata.author)) { + error(`${entry.relDir}/SKILL.md: Community skills must set 'metadata.author' (attribution for the registry)`); } } if (errors.length === 0) { @@ -177,10 +211,10 @@ function checkAgentFrontmatter(agentFiles) { } } -function checkSkillSections(skillDirs) { +function checkSkillSections(skillEntries) { console.log('\n📑 Required skill sections (## When to Use, ## Procedure):'); - for (const dir of skillDirs) { - const skillMd = path.join(SKILLS_DIR, dir, 'SKILL.md'); + for (const entry of skillEntries) { + const skillMd = skillMdPathFor(entry); if (!fs.existsSync(skillMd)) continue; const parsed = parseFrontmatter(skillMd); @@ -189,7 +223,7 @@ function checkSkillSections(skillDirs) { const content = parsed.content; if (!content.includes('## When to Use')) { - warn(`${dir}/SKILL.md: Missing '## When to Use' section`); + warn(`${entry.relDir}/SKILL.md: Missing '## When to Use' section`); } // Accept "## Procedure" or equivalent procedural sections @@ -197,7 +231,7 @@ function checkSkillSections(skillDirs) { content.includes('## Execution Playbook') || content.includes('## Command Playbook'); if (!hasProcedure) { - warn(`${dir}/SKILL.md: Missing '## Procedure' section (or equivalent like '## Execution Playbook')`); + warn(`${entry.relDir}/SKILL.md: Missing '## Procedure' section (or equivalent like '## Execution Playbook')`); } } } @@ -221,10 +255,10 @@ function checkAgentSections(agentFiles) { } } -function checkCrossReferences(skillDirs, agentFiles) { +function checkCrossReferences(skillEntries, agentFiles) { console.log('\n🔗 Cross-reference integrity:'); - const skillNames = new Set(skillDirs); + const skillNames = new Set(skillEntries.map((e) => e.leaf)); // Check agent -> agent references const agentNameMap = new Map(); @@ -261,8 +295,8 @@ function checkCrossReferences(skillDirs, agentFiles) { } // Check skill -> skill slash-command references - for (const dir of skillDirs) { - const skillMd = path.join(SKILLS_DIR, dir, 'SKILL.md'); + for (const entry of skillEntries) { + const skillMd = skillMdPathFor(entry); if (!fs.existsSync(skillMd)) continue; const parsed = parseFrontmatter(skillMd); @@ -271,7 +305,7 @@ function checkCrossReferences(skillDirs, agentFiles) { const slashCommands = extractSlashCommands(parsed.content); for (const cmd of slashCommands) { if (!skillNames.has(cmd)) { - warn(`${dir}/SKILL.md: Slash-command '/${cmd}' does not match any skill directory`); + warn(`${entry.relDir}/SKILL.md: Slash-command '/${cmd}' does not match any skill directory`); } } } @@ -281,14 +315,14 @@ function checkCrossReferences(skillDirs, agentFiles) { } } -function checkRelativeLinks(skillDirs, agentFiles) { +function checkRelativeLinks(skillEntries, agentFiles) { console.log('\n🔗 Relative link validation:'); let linkCount = 0; let brokenCount = 0; // Check skills - for (const dir of skillDirs) { - const skillMd = path.join(SKILLS_DIR, dir, 'SKILL.md'); + for (const entry of skillEntries) { + const skillMd = skillMdPathFor(entry); if (!fs.existsSync(skillMd)) continue; const raw = fs.readFileSync(skillMd, 'utf-8'); @@ -297,7 +331,7 @@ function checkRelativeLinks(skillDirs, agentFiles) { linkCount++; const resolved = path.resolve(path.dirname(skillMd), link); if (!fs.existsSync(resolved)) { - error(`${dir}/SKILL.md: Broken relative link '${link}'`); + error(`${entry.relDir}/SKILL.md: Broken relative link '${link}'`); brokenCount++; } } @@ -332,20 +366,21 @@ function main() { console.log(` Skills: ${SKILLS_DIR}`); console.log(` Agents: ${AGENTS_DIR}`); - const skillDirs = getSkillDirs(); + const skillEntries = getSkillEntries(); const agentFiles = getAgentFiles(); - console.log(`\n Found ${skillDirs.length} skill directories`); + const communityCount = skillEntries.filter((e) => e.tier === 'community').length; + console.log(`\n Found ${skillEntries.length} skill directories (${communityCount} community)`); console.log(` Found ${agentFiles.length} agent files`); - checkKebabCase(skillDirs, 'skill'); - checkSkillPresence(skillDirs); - checkSkillFrontmatter(skillDirs); + checkKebabCase(skillEntries.map((e) => e.leaf), 'skill'); + checkSkillPresence(skillEntries); + checkSkillFrontmatter(skillEntries); checkAgentFrontmatter(agentFiles); - checkSkillSections(skillDirs); + checkSkillSections(skillEntries); checkAgentSections(agentFiles); - checkCrossReferences(skillDirs, agentFiles); - checkRelativeLinks(skillDirs, agentFiles); + checkCrossReferences(skillEntries, agentFiles); + checkRelativeLinks(skillEntries, agentFiles); // Summary console.log('\n' + '─'.repeat(60)); diff --git a/website/docs/authoring/skills.md b/website/docs/authoring/skills.md index 0a68055..7039666 100644 --- a/website/docs/authoring/skills.md +++ b/website/docs/authoring/skills.md @@ -138,8 +138,26 @@ What the user (or agent) should invoke after a successful run. Use a clickable c | `argument-hint` | ⚪ | Free-text hint displayed in the prompt picker. | | `user-invocable` | ⚪ | Defaults to `true`. Set `false` for skills that only run as a sub-step of an agent and should not be surfaced standalone. | | `license` | ⚪ | Recommended `MIT` for skills shipped with this repo — keeps redistribution rights explicit. | -| `metadata.author` | ⚪ | Free-text author or team name (e.g. `Git-Ape`, `Microsoft`). | +| `metadata.author` | ⚪ for first-party, **✅ required** for community skills | Free-text author or team name (e.g. `Git-Ape`, `Microsoft`, or your GitHub handle). | | `metadata.version` | ⚪ | Semver string. Bump on every behavior change — eval suites and CI can pin to a version. | +| `metadata.source` | ⚪ | Community skills only. URL of the repo where the skill is canonically maintained, if not this one. | +| `metadata.maturity` | ⚪ | Community skills only. `experimental` (default) or `stable`. | + +## Community skills + +Third parties can contribute skills without maintainer sign-off by placing +them under `.github/skills/community//SKILL.md` instead of +`.github/skills//SKILL.md`. They follow the identical file layout and +frontmatter rules above, plus a required `metadata.author`. See +[Contributing a Community Skill](https://github.com/Azure/git-ape/blob/main/CONTRIBUTING.md#contributing-a-community-skill) +for the full process and start from +[`COMMUNITY_SKILL.template.md`](https://github.com/Azure/git-ape/blob/main/.github/templates/COMMUNITY_SKILL.template.md). + +Every skill — first-party and community — is picked up automatically into +the generated [Skill Registry](/docs/skills/registry) +(`.github/skills/registry.json`), which lists name, tier, author, and +maturity for discovery by users and tooling. No manual registration step +beyond adding a valid `SKILL.md` is required. ## Anatomy of a good skill diff --git a/website/docs/skills/overview.md b/website/docs/skills/overview.md index a9d86d8..7a40357 100644 --- a/website/docs/skills/overview.md +++ b/website/docs/skills/overview.md @@ -12,6 +12,8 @@ description: "Overview of all Git-Ape skills organized by deployment phase" Skills are focused capabilities invoked by agents at specific stages of the deployment workflow. Each skill handles one task. +> 📇 See the [Skill Registry](./registry) for the full machine-readable catalog (first-party + community) with author and maturity metadata. + ## Pre-Deploy Skills | Skill | Description | Invocable | diff --git a/website/docs/skills/registry.md b/website/docs/skills/registry.md new file mode 100644 index 0000000..497c0ea --- /dev/null +++ b/website/docs/skills/registry.md @@ -0,0 +1,48 @@ +--- +title: "Skill Registry" +sidebar_label: "Registry" +sidebar_position: 2 +description: "Machine-readable catalog of every Git-Ape skill, first-party and community" +--- + + + + +# Skill Registry + +A single catalog of every skill known to Git-Ape — first-party skills maintained in this repository and third-party skills contributed by the community under `.github/skills/community/`. + +The raw, machine-readable version lives at [`.github/skills/registry.json`](https://github.com/Azure/git-ape/blob/main/.github/skills/registry.json) and is regenerated by `node scripts/generate-docs.js` on every change under `.github/skills/`. + +## First-Party Skills (15) + +| Skill | Tier | Author | Maturity | Description | +|-------|------|--------|----------|-------------| +| [Azure Cost Estimator](./azure-cost-estimator) | first-party | Microsoft | stable | Estimate monthly costs for Azure resources by querying the Azure Retail Prices API. Parses ARM templates to identify resources, SKUs, and regions, then looks up real retail pricing. Produces a per-resource cost breakdown with monthly totals. Use during template generation or when user asks about costs. | +| [Azure Deployment Preflight](./azure-deployment-preflight) | first-party | Microsoft | stable | Run preflight validation on ARM templates before deployment. Performs what-if analysis, permission checks, and generates a structured report with resource changes (create/modify/delete). Use before any deployment to preview changes and catch issues early. | +| [Azure Drift Detector](./azure-drift-detector) | first-party | Microsoft | stable | Detect configuration drift between deployed Azure resources and stored deployment state. Compare actual Azure configuration against desired state in .azure/deployments/, identify differences, and guide user through reconciliation options. Use when checking for manual changes, policy remediations, or unauthorized modifications. | +| [Azure Integration Tester](./azure-integration-tester) | first-party | Microsoft | stable | Run post-deployment integration tests for Azure resources. Verify Function Apps, Storage Accounts, Databases, App Services are healthy and accessible. Use after successful Azure deployment. | +| [Azure Naming Research](./azure-naming-research) | first-party | Microsoft | stable | Research Azure naming constraints and CAF abbreviations for a given resource type. Use when you need to look up the official CAF slug, naming rules (length, scope, valid characters), and derive validation/cleaning regex patterns for an Azure resource. Triggers on: CAF abbreviation lookup, Azure naming rules research, resource naming constraints. | +| [Azure Policy Advisor](./azure-policy-advisor) | first-party | Microsoft | stable | Assess ARM template resources for Azure Policy compliance. Analyse the template, query existing subscription assignments via `az policy assignment list`, identify unassigned built-in and custom policies (CIS, NIST, FedRAMP), and emit a two-part report: template-fixable gaps (Part 1) and subscription-level policy assignments (Part 2). USE FOR: recommending Azure Policy assignments for an ARM template, auditing a subscription against CIS/NIST/general best practices, deciding which initiatives to assign at sub or management-group scope, distinguishing template-fixable vs platform-level governance gaps. DO NOT USE FOR: per-resource security configuration assessment (use azure-security-analyzer), RBAC role recommendations (use azure-role-selector), CAF naming abbreviations (use azure-naming-research), or pricing estimates (use azure-cost-estimator). INVOKES: az policy assignment list, az policy set-definition list, microsoft_docs_search, microsoft_docs_fetch. | +| [Azure Resource Availability](./azure-resource-availability) | first-party | Microsoft | stable | Query live Azure APIs to validate resource availability before template generation or deployment. Checks VM SKU restrictions, Kubernetes/runtime version support, API version compatibility, and subscription quota. Use during requirements gathering and preflight to catch deployment failures early. | +| [Azure Resource Visualizer](./azure-resource-visualizer) | first-party | Microsoft | stable | Analyze deployed Azure resource groups and generate detailed Mermaid architecture diagrams showing relationships between resources. Use for post-deployment visualization, understanding existing infrastructure, or documenting live Azure environments. | +| [Azure Rest Api Reference](./azure-rest-api-reference) | first-party | Microsoft | stable | Look up Azure REST API and ARM template reference documentation for any resource type. Returns exact property schemas, required fields, valid values, and latest stable API versions. Use BEFORE generating or modifying ARM templates to ensure correctness. No Azure connection required. | +| [Azure Role Selector](./azure-role-selector) | first-party | Microsoft | stable | Recommend least-privilege Azure RBAC roles for deployed resources. Finds minimal built-in roles matching desired permissions or creates custom role definitions. Use during security analysis or when configuring access for service principals and managed identities. | +| [Azure Security Analyzer](./azure-security-analyzer) | first-party | Microsoft | stable | Analyze Azure resource configurations against security best practices using Azure MCP bestpractices service. Produces per-resource security assessment with severity ratings and recommendations. Use during template generation before deployment confirmation. | +| [Azure Stack Deploy](./azure-stack-deploy) | first-party | Microsoft | stable | Run an Azure Deployment Stack create (subscription scope) for a prepared Git-Ape deployment artifact and write state.json (schemaVersion 1.0). Use locally so the result matches the CI deploy workflow. | +| [Azure Stack Destroy](./azure-stack-destroy) | first-party | Microsoft | stable | Tear down a Git-Ape deployment by ID. Reads `state.json` under `.azure/deployments//` to delete the Azure Deployment Stack and purge soft-deleted Key Vault / Cognitive Services. Refuses to run without `state.json`. Use for any local CLI or VS Code Git-Ape teardown so the result matches the CI destroy workflow. | +| [Git Ape Onboarding](./git-ape-onboarding) | first-party | Microsoft | stable | Bootstrap a GitHub repository for Git-Ape CI/CD: Entra app registration, OIDC federated credentials, RBAC role assignments, GitHub environments (azure-deploy/azure-destroy), required secrets, and scaffold Actions workflow files — plus enterprise-wide distribution via a `.github-private` repo (managed-settings.json plugin standards + custom agents). USE FOR: first-time Git-Ape setup, new subscription onboarding, multi-environment (dev/staging/prod) setup, configure OIDC, federated credentials, RBAC setup, GitHub environments, scaffold workflow files, rolling Git-Ape out org/enterprise-wide. DO NOT USE FOR: deploying resources (use git-ape), drift detection alone, secret rotation. | +| [Prereq Check](./prereq-check) | first-party | Git-Ape | stable | Validate Git-Ape CLI tool installation (az, gh, jq, git), versions, and auth sessions. Shows platform-specific install commands for anything missing. USE FOR: check Git-Ape prerequisites, what do I need to install for Git-Ape, verify Git-Ape CLI tools, az: command not found, gh: command not found, jq: command not found, git: command not found, az missing, gh missing, jq missing, git missing, fresh machine setup for Git-Ape, dev container setup for Git-Ape, before running git-ape-onboarding, az login required, gh auth login, auth expired, not logged in, outdated az version, minimum az version, upgrade az. DO NOT USE FOR: Anything else. This skill is narrowly scoped to prerequisites checks for Git-Ape's CLI tools and auth sessions. Do not use it for any other purpose. | + +## Community Skills (0) + +Third-party skills. **Not** maintained by the Git-Ape maintainers — check the Author column for who to contact, and review the skill's source before relying on it. + +_No community skills have been contributed yet. See [Contributing a Community Skill](https://github.com/Azure/git-ape/blob/main/CONTRIBUTING.md#contributing-a-community-skill) to add the first one._ + +## Contributing to the Registry + +- **First-party skills** live under `.github/skills//SKILL.md` and are maintained by the Git-Ape team. +- **Community skills** live under `.github/skills/community//SKILL.md` and are contributed by third parties via pull request, following the same [contribution guidelines](https://github.com/Azure/git-ape/blob/main/CONTRIBUTING.md#contributing-a-community-skill) and CI checks as first-party skills. + +Both tiers are picked up automatically — no manual registration step is required beyond adding the `SKILL.md` file with valid frontmatter. From 7429d831b69a07c34e8118b3fa723fa70d8a20e9 Mon Sep 17 00:00:00 2001 From: David Wright Date: Tue, 22 Sep 2026 13:33:35 +1000 Subject: [PATCH 2/2] Add community skills docs category Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- website/docs/skills/community/_category_.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 website/docs/skills/community/_category_.json diff --git a/website/docs/skills/community/_category_.json b/website/docs/skills/community/_category_.json new file mode 100644 index 0000000..714c64b --- /dev/null +++ b/website/docs/skills/community/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Community Skills", + "position": 99, + "link": { + "type": "generated-index", + "description": "Third-party skills contributed by the community." + } +}