AgentSkillsCN

registry-manager

通过扫描已安装的技能并更新 skills.json,持续维护技能注册表。当您需要添加、移除或审计技能时,请使用本指南。

SKILL.md
--- frontmatter
name: registry-manager
description: Maintain the skills registry by scanning installed skills and updating skills.json. Use when adding, removing, or auditing skills.

Registry Manager v1.1.0

Core Principle

Single source of truth. Scan → Parse → Update → Validate.

The registry is auto-generated from scanning SKILL.md files. Never edit it manually—always regenerate.


When to Use

  • After installing new skills
  • After removing skills
  • To audit what skills are available
  • When registry files are missing or outdated
  • During project setup

Output Files

The rebuild script generates two files:

FileSizePurpose
skills.json~20KBFull registry for tools, validation, CI
skills-index.json~5KBCompact index optimized for LLMs

skills-index.json (LLM-optimized)

json
{
  "v": "1.1.0",
  "n": 30,
  "skills": {
    "core": [
      {"id": "bd", "desc": "Backlog management...", "path": ".claude/core/bd"},
      {"id": "git", "desc": "Git hygiene...", "path": ".claude/core/git"}
    ],
    "devops": [...]
  }
}

Benefits:

  • 71% smaller than full registry
  • Grouped by category for fast scanning
  • Truncated descriptions (120 chars max)
  • Short keys (v, n, desc) save tokens

Skills Locations

Skills can exist in multiple locations:

code
project/
├── .claude/
│   ├── skills.json           ← Full registry
│   ├── skills-index.json     ← LLM-optimized index
│   ├── rebuild-registry.js   ← Rebuild script
│   └── skills/               ← Primary location
│       ├── core/
│       ├── engineering/
│       └── [any-category]/   ← Categories auto-detected
│
└── .github/
    └── skills/               ← Alternative location
        └── [any-category]/

Rules:

  • Categories are auto-detected from folder names
  • Skills are deduplicated by id (first found wins)
  • Both outputs always go to .claude/

Quick Rebuild (Recommended)

bash
cd .claude && node rebuild-registry.js

Output:

code
╔════════════════════════════════════╗
║   Skills Registry Rebuild v1.1.0   ║
╚════════════════════════════════════╝

Scanning locations:
  📁 .claude/skills/
  📁 .github/skills/

  ✓ bd (.claude)
  ✓ git (.claude)
  ...

────────────────────────────────────
✅ Registry updated: 30 skills

📄 Generated files:
   skills.json       19.7KB (full)
   skills-index.json 5.7KB (LLM-optimized)

📊 By category:
   core: 15
   engineering: 4
   ...
────────────────────────────────────

Full Rebuild Process (Manual)

Execute these steps in order, one skill at a time.

Step 0: Initialize Registry

bash
# Create empty registry if missing
cat > .claude/skills.json << 'EOF'
{
  "version": "1.0.0",
  "updated_at": "",
  "skills": []
}
EOF

Step 1: Discover All Skills

bash
# Find all SKILL.md files
find .claude/skills -name "SKILL.md" -type f | sort

Record the list. Process each file one by one in the following steps.

Step 2: Parse One Skill

For each SKILL.md file found, extract metadata:

bash
# Example: parsing a single skill
SKILL_FILE=".claude/skills/core/git/SKILL.md"

# Extract frontmatter (lines between --- markers)
sed -n '/^---$/,/^---$/p' "$SKILL_FILE" | head -20

Extract these fields from frontmatter:

FieldSourceRequired
idname field in frontmatter✅ Yes
nameConvert id to Title Case✅ Yes
descriptiondescription field✅ Yes
categoryParent folder name✅ Yes
pathRelative path from .claude/✅ Yes
tagsExtract from description keywordsOptional
triggersFrom "When to Use" sectionOptional
dependenciesLook for skill referencesOptional

Step 3: Build Skill Entry

Transform parsed data into a registry entry:

json
{
  "id": "git",
  "name": "Git",
  "description": "Git hygiene for multi-agent work. Use when committing, pushing, or syncing with remote.",
  "category": "core",
  "tags": ["git", "version-control", "sync"],
  "triggers": ["git pull", "git push", "commit", "sync remote"],
  "path": "skills/core/git/SKILL.md",
  "dependencies": []
}

Step 4: Validate Entry

Before adding, verify:

code
□ id matches folder name
□ id is lowercase with hyphens only
□ description is under 300 chars
□ path points to existing file
□ category matches parent folder
□ no duplicate id in registry

If validation fails: Log the error, skip this skill, continue to next.

Step 5: Add to Registry

Append the validated entry to the skills array.

Step 6: Repeat for All Skills

Go back to Step 2 for the next SKILL.md file until all are processed.

Step 7: Finalize Registry

bash
# Update timestamp
# The final skills.json should look like:
{
  "version": "1.0.0",
  "updated_at": "2026-01-25T12:00:00Z",
  "skills": [
    { /* skill 1 */ },
    { /* skill 2 */ },
    ...
  ]
}

Quick Rebuild Script

Use this Node.js script for automated rebuilding:

javascript
#!/usr/bin/env node
// rebuild-registry.js

const fs = require('fs');
const path = require('path');

const SKILLS_DIR = '.claude/skills';
const REGISTRY_PATH = '.claude/skills.json';
const CATEGORIES = ['core', 'engineering', 'devops', 'documentation', 'research'];

function parseFrontmatter(content) {
  const match = content.match(/^---\n([\s\S]*?)\n---/);
  if (!match) return null;
  
  const frontmatter = {};
  match[1].split('\n').forEach(line => {
    const [key, ...rest] = line.split(':');
    if (key && rest.length) {
      frontmatter[key.trim()] = rest.join(':').trim();
    }
  });
  return frontmatter;
}

function extractTriggers(content) {
  const whenToUse = content.match(/## When to Use[\s\S]*?(?=\n## |$)/);
  if (!whenToUse) return [];
  
  const triggers = [];
  const lines = whenToUse[0].split('\n');
  lines.forEach(line => {
    const match = line.match(/^[-*]\s+(.+)/);
    if (match) {
      // Extract key phrases
      const phrase = match[1].toLowerCase()
        .replace(/when |use |for |to /g, '')
        .trim()
        .slice(0, 50);
      if (phrase.length > 3) triggers.push(phrase);
    }
  });
  return triggers.slice(0, 5);
}

function extractTags(description, id) {
  const words = description.toLowerCase()
    .replace(/[^\w\s-]/g, '')
    .split(/\s+/)
    .filter(w => w.length > 3 && !['when', 'with', 'this', 'that', 'from', 'using'].includes(w));
  
  const tags = [...new Set([id, ...words.slice(0, 4)])];
  return tags.slice(0, 6);
}

function scanSkills() {
  const skills = [];
  const errors = [];
  
  CATEGORIES.forEach(category => {
    const categoryPath = path.join(SKILLS_DIR, category);
    if (!fs.existsSync(categoryPath)) return;
    
    const folders = fs.readdirSync(categoryPath, { withFileTypes: true })
      .filter(d => d.isDirectory());
    
    folders.forEach(folder => {
      const skillPath = path.join(categoryPath, folder.name, 'SKILL.md');
      
      if (!fs.existsSync(skillPath)) {
        errors.push(`Missing SKILL.md: ${skillPath}`);
        return;
      }
      
      try {
        const content = fs.readFileSync(skillPath, 'utf-8');
        const frontmatter = parseFrontmatter(content);
        
        if (!frontmatter || !frontmatter.name || !frontmatter.description) {
          errors.push(`Invalid frontmatter: ${skillPath}`);
          return;
        }
        
        // Validate id matches folder
        if (frontmatter.name !== folder.name) {
          errors.push(`Name mismatch: ${frontmatter.name} vs folder ${folder.name}`);
        }
        
        const skill = {
          id: frontmatter.name,
          name: frontmatter.name
            .split('-')
            .map(w => w.charAt(0).toUpperCase() + w.slice(1))
            .join(' '),
          description: frontmatter.description.slice(0, 300),
          category: category,
          tags: extractTags(frontmatter.description, frontmatter.name),
          triggers: extractTriggers(content),
          path: `skills/${category}/${folder.name}/SKILL.md`,
          dependencies: []
        };
        
        skills.push(skill);
        console.log(`✓ ${skill.id}`);
        
      } catch (err) {
        errors.push(`Error parsing ${skillPath}: ${err.message}`);
      }
    });
  });
  
  return { skills, errors };
}

function main() {
  console.log('Scanning skills...\n');
  
  const { skills, errors } = scanSkills();
  
  const registry = {
    version: '1.0.0',
    updated_at: new Date().toISOString(),
    skills: skills.sort((a, b) => a.id.localeCompare(b.id))
  };
  
  fs.writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2));
  
  console.log(`\n✅ Registry updated: ${skills.length} skills`);
  
  if (errors.length) {
    console.log(`\n⚠️  Errors (${errors.length}):`);
    errors.forEach(e => console.log(`   - ${e}`));
  }
}

main();

Run it:

bash
node rebuild-registry.js

Manual Workflow (No Script)

If you can't run scripts, follow this systematic process:

1. List All Skills

bash
find .claude/skills -name "SKILL.md" | while read f; do
  echo "---"
  echo "FILE: $f"
  head -10 "$f"
done

2. For Each Skill, Create Entry

Read the file, extract:

  • name from frontmatter → becomes id
  • description from frontmatter
  • folder path → determines category and path

3. Assemble JSON

Build the registry manually, one entry at a time:

json
{
  "version": "1.0.0",
  "updated_at": "2026-01-25T12:00:00Z",
  "skills": [
    {
      "id": "bd",
      "name": "BD (Beads)",
      "description": "Backlog management with bd utility...",
      "category": "core",
      "tags": ["backlog", "tasks"],
      "triggers": ["create issue", "backlog"],
      "path": "skills/core/bd/SKILL.md",
      "dependencies": []
    }
  ]
}

4. Validate JSON

bash
# Check JSON syntax
cat .claude/skills.json | python3 -m json.tool > /dev/null && echo "✓ Valid JSON"

# Validate against schema (if jq available)
# Or just visually verify structure

Adding a Single Skill

When adding just one new skill:

1. Parse the New Skill

bash
SKILL_PATH=".claude/skills/core/new-skill/SKILL.md"
head -20 "$SKILL_PATH"

2. Create Entry

json
{
  "id": "new-skill",
  "name": "New Skill",
  "description": "...",
  "category": "core",
  "tags": [],
  "triggers": [],
  "path": "skills/core/new-skill/SKILL.md",
  "dependencies": []
}

3. Insert into Registry

Add to the skills array in skills.json, maintaining alphabetical order by id.

4. Update Timestamp

json
"updated_at": "2026-01-25T12:00:00Z"

Removing a Skill

1. Delete the Folder

bash
rm -rf .claude/skills/core/old-skill

2. Remove from Registry

Delete the corresponding entry from skills.json.

3. Update Timestamp


Audit / Verify

Check for inconsistencies:

bash
# Skills on disk but not in registry
find .claude/skills -name "SKILL.md" -exec dirname {} \; | sort > /tmp/disk.txt

# Skills in registry
cat .claude/skills.json | grep '"path"' | sed 's/.*skills\///' | sed 's/\/SKILL.md.*//' | sort > /tmp/registry.txt

# Compare
diff /tmp/disk.txt /tmp/registry.txt

If differences found: Run full rebuild.


Registry Entry Template

Copy this for new entries:

json
{
  "id": "",
  "name": "",
  "description": "",
  "category": "",
  "tags": [],
  "triggers": [],
  "path": "",
  "dependencies": []
}

Field guide:

FieldExampleNotes
id"code-review"Matches folder name, lowercase-hyphen
name"Code Review"Human readable
description"Systematic code review. Use when reviewing PRs."WHAT + WHEN
category"core"One of: core, engineering, devops, documentation, research
tags["review", "quality"]Searchable keywords
triggers["review code", "PR review"]Activation phrases
path"skills/core/code-review/SKILL.md"Relative to .claude/
dependencies["git"]Other skill IDs needed first

Quick Reference

code
Commands:
  Full rebuild    → node rebuild-registry.js
  Find skills     → find .claude/skills -name "SKILL.md"
  Validate JSON   → python3 -m json.tool < skills.json
  Audit           → diff disk vs registry

Locations:
  Registry        → .claude/skills.json
  Schema          → .claude/skills.schema.json
  Skills          → .claude/skills/{category}/{skill-id}/SKILL.md

Process:
  1. Scan: find all SKILL.md files
  2. Parse: extract frontmatter
  3. Transform: build entry object
  4. Validate: check required fields
  5. Insert: add to skills array
  6. Save: update skills.json with timestamp