#!/usr/bin/env node /** * Build System for Cross-Provider Design Skills * * Transforms source skills into provider-specific formats: * - Cursor: .cursor/skills/ * - Claude Code: .claude/skills/ * - Gemini: .gemini/skills/ * - Codex: dist/codex/ only (OpenAI-metadata bundle; not synced to repo root) * - Agents: .agents/skills/ (Codex repo/user installs) * - GitHub: .github/skills/ (GitHub Copilot) * * Also assembles a universal ZIP containing all providers, * and builds Tailwind CSS for production deployment. */ import path from 'path'; import fs from 'fs'; import { fileURLToPath } from 'url'; import { readSourceFiles, readPatterns, stashPerProjectArtifacts, restorePerProjectArtifacts } from './lib/utils.js'; import { createTransformer, PROVIDERS } from './lib/transformers/index.js'; import { createAllZips } from './lib/zip.js'; import { ANTIPATTERNS } from '../cli/engine/registry/antipatterns.mjs'; // Sub-page generation is now handled by Astro content collections. /** * Generate authoritative counts from source data and write to site/public/js/generated/counts.js. * Also validates that key HTML files reference the correct numbers. */ function generateCounts(rootDir, skills, buildDir) { // Count active commands. After the v3.0 consolidation, commands are sub-commands // of /impeccable. Count them from the command router table in SKILL.md. const impeccableSkill = skills.find(s => s.name === 'impeccable'); let commandCount; if (impeccableSkill) { // Count lines in the command table that start with | `...` | — tolerant // of argument hints inside the backticks (e.g. `craft [feature]`) and of // multi-word commands (e.g. `pin `). const routerMatches = impeccableSkill.body.match(/^\| `[^`]+` \|/gm); commandCount = routerMatches ? routerMatches.length : 0; } else { // Fallback: count user-invocable skills const activeCommands = skills.filter(s => { if (!s.userInvocable) return false; const content = fs.readFileSync(s.filePath, 'utf-8'); return !content.includes('DEPRECATED'); }); commandCount = activeCommands.length; } // Count detection rules from the detector registry. const detectionCount = new Set(ANTIPATTERNS.map(rule => rule.id)).size; // Write generated counts module const genDir = path.join(rootDir, 'site/public/js/generated'); fs.mkdirSync(genDir, { recursive: true }); fs.writeFileSync(path.join(genDir, 'counts.js'), `// GENERATED by build.js — do not edit\n` + `export const COMMAND_COUNT = ${commandCount};\n` + `export const DETECTION_COUNT = ${detectionCount};\n` ); // Validate counts in key files const filesToCheck = [ 'site/pages/index.astro', 'README.md', 'NOTICE.md', 'AGENTS.md', '.claude-plugin/plugin.json', '.claude-plugin/marketplace.json', ]; let errors = 0; for (const relPath of filesToCheck) { const absPath = path.join(rootDir, relPath); if (!fs.existsSync(absPath)) continue; const content = fs.readFileSync(absPath, 'utf-8'); // Check for stale command counts (look for "N commands" or "N skills" patterns) // Strip changelog list content to avoid flagging historical counts const strippedContent = content.replace(/