#!/usr/bin/env node
/**
* Generate Skill - Auto-generate skill from repo documentation
*
* Usage:
* node scripts/generate-skill.js <repo-name>
* node scripts/generate-skill.js <repo-name> --force # Overwrite existing
* node scripts/generate-skill.js <repo-name> --dry-run # Preview without writing
*/
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'
import { join, dirname } from 'path'
import { fileURLToPath } from 'url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const KB_ROOT = join(__dirname, '..')
const SKILLS_DIR = join(KB_ROOT, '.claude', 'skills')
const HOLEPUNCHTO_DIR = join(KB_ROOT, 'holepunchto')
// Load semantic data
function loadJSON(filename) {
const path = join(HOLEPUNCHTO_DIR, filename)
if (!existsSync(path)) {
console.error(`❌ File not found: ${filename}`)
return null
}
return JSON.parse(readFileSync(path, 'utf-8'))
}
const REPO_MAP = loadJSON('repo_map.json')
const CATEGORIES = loadJSON('semantic-categories.json')
const DEPENDENCIES = loadJSON('dependency-graph.json')
const PATTERNS = loadJSON('pattern-map.json')
// Find repo in repo_map
function findRepo(repoName) {
if (!REPO_MAP || !REPO_MAP.repos) return null
return REPO_MAP.repos.find(r => r.name === repoName)
}
// Get category info
function getCategory(repoName) {
if (!CATEGORIES) return { category: 'infrastructure', subcategory: 'miscellaneous' }
const cat = CATEGORIES[repoName]
if (!cat) return { category: 'infrastructure', subcategory: 'miscellaneous' }
return {
category: cat.category.toLowerCase().replace(/\s+/g, '-'),
subcategory: cat.subcategory || 'general'
}
}
// Get dependencies
function getDependencies(repoName) {
if (!DEPENDENCIES || !DEPENDENCIES[repoName]) return []
const deps = DEPENDENCIES[repoName].dependencies || {}
// Filter to only holepunchto packages
return Object.keys(deps).filter(d =>
REPO_MAP && REPO_MAP.repos && REPO_MAP.repos.find(r => r.name === d)
)
}
// Get patterns
function getPatterns(repoName) {
if (!PATTERNS || !PATTERNS.repoPatterns || !PATTERNS.repoPatterns[repoName]) return []
return PATTERNS.repoPatterns[repoName].implements || []
}
// Determine priority from category and stars
function determinePriority(repo, category) {
if (!repo) return 'medium'
const stars = repo.stars || 0
const cat = category.category
// Critical infrastructure
if (cat === 'building-blocks') return 'critical'
if (cat === 'helpers' && stars > 100) return 'high'
if (cat === 'pear-modules' && stars > 50) return 'high'
// High usage
if (stars > 500) return 'critical'
if (stars > 200) return 'high'
if (stars > 50) return 'medium'
return 'low'
}
// Read existing holepunchto skills.md if available
function readHolepunchtoDoc(repoName) {
const docPath = join(HOLEPUNCHTO_DIR, 'skills', repoName, 'skills.md')
if (!existsSync(docPath)) return null
return readFileSync(docPath, 'utf-8')
}
// Extract examples from holepunchto docs
function extractExamples(doc) {
if (!doc) return []
const examples = []
const codeBlockRegex = /```(?:javascript|js)?\n([\s\S]*?)```/g
let match
while ((match = codeBlockRegex.exec(doc)) !== null) {
const code = match[1].trim()
if (code.length > 20 && code.length < 500) { // Reasonable example size
examples.push(code)
}
}
return examples.slice(0, 3) // Max 3 examples
}
// Generate "When to Use" section
function generateWhenToUse(repo, category, patterns) {
const uses = []
// Based on category
if (category.category === 'building-blocks') {
uses.push('Building P2P applications requiring distributed data structures')
}
if (category.category === 'helpers') {
uses.push('Simplifying common P2P development tasks')
}
if (category.category === 'bare-modules') {
uses.push('Using Bare runtime for P2P applications')
}
if (category.category === 'pear-modules') {
uses.push('Building Pear applications')
}
// Based on patterns
if (patterns.includes('append-only-log')) {
uses.push('Implementing append-only data structures')
}
if (patterns.includes('replication')) {
uses.push('Syncing data between peers')
}
if (patterns.includes('dht')) {
uses.push('P2P peer discovery and routing')
}
if (patterns.includes('encryption')) {
uses.push('Securing P2P connections')
}
// Fallback based on description
if (uses.length === 0 && repo && repo.description) {
const desc = repo.description.toLowerCase()
if (desc.includes('test')) uses.push('Testing P2P applications')
if (desc.includes('util')) uses.push('Common utility operations')
if (desc.includes('stream')) uses.push('Working with data streams')
if (desc.includes('encode')) uses.push('Encoding and decoding data')
}
// Generic fallback
if (uses.length === 0) {
uses.push('Building P2P applications with Holepunchto ecosystem')
}
return uses.slice(0, 5) // Max 5 use cases
}
// Generate skill content
function generateSkillContent(repoName, options = {}) {
const repo = findRepo(repoName)
if (!repo) {
console.error(`❌ Repo not found in repo_map.json: ${repoName}`)
return null
}
const category = getCategory(repoName)
const dependencies = getDependencies(repoName)
const patterns = getPatterns(repoName)
const priority = determinePriority(repo, category)
const holepunchtoDoc = readHolepunchtoDoc(repoName)
const examples = extractExamples(holepunchtoDoc)
const whenToUse = generateWhenToUse(repo, category, patterns)
// Build skill markdown
let content = `---
name: ${repoName}
description: ${repo.description || 'Holepunchto module'}
category: ${category.category}
priority: ${priority}
stars: ${repo.stars || 0}
---
# ${repoName.charAt(0).toUpperCase() + repoName.slice(1)} Skill
${repo.description || 'Part of the Holepunchto P2P ecosystem.'}
## When to Use
Use ${repoName} when you need:
`
whenToUse.forEach(use => {
content += `- **${use.split(' ').slice(0, 3).join(' ')}** - ${use}\n`
})
content += '\n## Quick Examples\n\n'
if (examples.length > 0) {
examples.forEach((example, i) => {
content += `### Example ${i + 1}\n\n\`\`\`javascript\n${example}\n\`\`\`\n\n`
})
} else {
content += `\`\`\`javascript
import ${repoName.replace(/-/g, '')} from '${repoName}'
// Basic usage
// See complete documentation for API details
\`\`\`
`
}
// Integration section
if (dependencies.length > 0) {
content += '## Integration with Other Skills\n\n'
content += `${repoName} commonly works with:\n\n`
dependencies.slice(0, 5).forEach(dep => {
content += `- **${dep}** - Dependency\n`
})
content += '\n'
}
// Patterns section
if (patterns.length > 0) {
content += '## P2P Patterns\n\n'
content += `This module implements:\n\n`
patterns.forEach(pattern => {
content += `- ${pattern.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}\n`
})
content += '\n'
}
// Documentation link
content += '## Complete Documentation\n\n'
content += `- **GitHub**: https://github.com/holepunchto/${repoName}\n`
content += `- **Holepunchto Skills**: See \`holepunchto/skills/${repoName}/skills.md\` for complete API\n`
return content
}
// Write skill file
function writeSkill(repoName, content, force = false) {
const skillDir = join(SKILLS_DIR, repoName)
const skillFile = join(skillDir, 'SKILL.md')
// Check if exists
if (existsSync(skillFile) && !force) {
console.error(`❌ Skill already exists: ${repoName}`)
console.error('Use --force to overwrite')
return false
}
// Create directory
if (!existsSync(skillDir)) {
mkdirSync(skillDir, { recursive: true })
}
// Write file
writeFileSync(skillFile, content, 'utf-8')
return true
}
// Main
function main() {
const args = process.argv.slice(2)
if (args.length === 0 || args[0] === '--help') {
console.log('Usage:')
console.log(' node generate-skill.js <repo-name>')
console.log(' node generate-skill.js <repo-name> --force')
console.log(' node generate-skill.js <repo-name> --dry-run')
process.exit(args.length === 0 ? 1 : 0)
}
const repoName = args[0]
const force = args.includes('--force')
const dryRun = args.includes('--dry-run')
console.log(`Generating skill for: ${repoName}`)
const content = generateSkillContent(repoName)
if (!content) {
process.exit(1)
}
if (dryRun) {
console.log('\n--- Preview (dry run) ---\n')
console.log(content)
console.log('\n--- End Preview ---\n')
return
}
const success = writeSkill(repoName, content, force)
if (success) {
console.log(`✅ Generated skill: ${repoName}`)
console.log(` File: .claude/skills/${repoName}/SKILL.md`)
} else {
process.exit(1)
}
}
main()