/** * Canonical skill categories for MioKit WebUI. * Aligned with skills/INDEX.md domain groupings. */ export const CANONICAL_CATEGORIES = [ 'workflow', 'frontend', 'backend', 'mobile', 'platform', 'infrastructure', 'security', 'mcp-agents', 'documentation', 'creative', 'meta', 'openspec', 'mioflow', 'mio-awesome', ] as const; export type SkillCategory = (typeof CANONICAL_CATEGORIES)[number]; /** Display order in filter dropdown */ export const CATEGORY_ORDER: SkillCategory[] = [...CANONICAL_CATEGORIES]; /** Map legacy frontmatter categories to canonical (fallback only) */ const LEGACY_CATEGORY_MAP: Record = { utilities: 'workflow', 'dev-tools': 'mcp-agents', frameworks: 'platform', 'ai-ml': 'creative', multimedia: 'creative', database: 'backend', infrastructure: 'infrastructure', frontend: 'frontend', backend: 'backend', security: 'security', other: 'creative', uncategorized: 'meta', }; /** Exact relative paths (without SKILL.md) → category */ const PATH_OVERRIDES: Record = { 'ak-mioflow': 'mioflow', 'mio-awesome-skill': 'mio-awesome', }; /** Longest-prefix-first path rules */ const PATH_PREFIX_RULES: Array<{ prefix: string; category: SkillCategory }> = [ { prefix: 'openspec-', category: 'openspec' }, { prefix: 'ak-mioflow/mioflow-skills/', category: 'mioflow' }, { prefix: 'mio-awesome-skill/mobile-expert/', category: 'mobile' }, { prefix: 'mio-awesome-skill/golang/', category: 'backend' }, { prefix: 'mio-awesome-skill/nestjs/', category: 'backend' }, { prefix: 'mio-awesome-skill/backend-extra/', category: 'backend' }, { prefix: 'mio-awesome-skill/frontend-extra/', category: 'frontend' }, { prefix: 'mio-awesome-skill/architecture/', category: 'meta' }, { prefix: 'mio-awesome-skill/workflow-extra/', category: 'workflow' }, { prefix: 'mio-awesome-skill/technical-extra/', category: 'infrastructure' }, { prefix: 'document-skills/', category: 'creative' }, ]; /** Top-level skill folder → canonical category */ const TOP_LEVEL_MAP: Record = { // Workflow 'ak-plan': 'workflow', 'ak-debug': 'workflow', cook: 'workflow', fix: 'workflow', 'code-review': 'workflow', test: 'workflow', ship: 'workflow', team: 'workflow', watzup: 'workflow', 'ak-scenario': 'workflow', 'ak-predict': 'workflow', 'ak-loop': 'workflow', 'ak-autoresearch': 'workflow', bootstrap: 'workflow', codebase: 'workflow', 'web-testing': 'workflow', // Frontend 'frontend-development': 'frontend', 'frontend-design': 'frontend', 'react-best-practices': 'frontend', 'ui-styling': 'frontend', 'web-design-guidelines': 'frontend', threejs: 'frontend', shader: 'frontend', stitch: 'frontend', remotion: 'frontend', // Backend 'backend-development': 'backend', databases: 'backend', 'better-auth': 'backend', 'payment-integration': 'backend', vnpay: 'backend', // Mobile 'mobile-development': 'mobile', // Platform / stack 'web-frameworks': 'platform', tanstack: 'platform', shopify: 'platform', // Infrastructure devops: 'infrastructure', deploy: 'infrastructure', git: 'infrastructure', worktree: 'infrastructure', // Security 'ak-security': 'security', 'security-scan': 'security', 'cti-expert': 'security', // MCP & agents 'mcp-builder': 'mcp-agents', 'mcp-management': 'mcp-agents', 'use-mcp': 'mcp-agents', agentize: 'mcp-agents', 'agent-browser': 'mcp-agents', 'ak-memories': 'mcp-agents', gkg: 'mcp-agents', graphify: 'mcp-agents', repomix: 'mcp-agents', scout: 'mcp-agents', xia: 'mcp-agents', 'google-adk-python': 'mcp-agents', 'chrome-devtools': 'mcp-agents', // Documentation docs: 'documentation', 'docs-seeker': 'documentation', llms: 'documentation', mintlify: 'documentation', journal: 'documentation', retro: 'documentation', 'project-management': 'documentation', 'project-organization': 'documentation', kanban: 'documentation', 'plans-kanban': 'documentation', preview: 'documentation', 'markdown-novel-viewer': 'documentation', // Creative & media 'ai-artist': 'creative', 'ai-multimodal': 'creative', design: 'creative', copywriting: 'creative', excalidraw: 'creative', 'mermaidjs-v11': 'creative', 'media-processing': 'creative', 'show-off': 'creative', // Meta & discovery ask: 'meta', brainstorm: 'meta', research: 'meta', 'sequential-thinking': 'meta', 'problem-solving': 'meta', 'context-engineering': 'meta', 'coding-level': 'meta', 'find-skills': 'meta', 'skill-creator': 'meta', 'template-skill': 'meta', // Bundles 'ak-mioflow': 'mioflow', 'mio-awesome-skill': 'mio-awesome', // OpenSpec (folder names) 'openspec-propose': 'openspec', 'openspec-explore': 'openspec', 'openspec-apply': 'openspec', 'openspec-archive': 'openspec', }; function getSkillDir(relativePath: string): string { return relativePath.replace(/\/SKILL\.md$/, ''); } function getTopLevelFolder(relativePath: string): string { const dir = getSkillDir(relativePath); return dir.split('/')[0] ?? dir; } export function resolveCategory(relativePath: string, frontmatterCategory?: string): SkillCategory { const dir = getSkillDir(relativePath); if (PATH_OVERRIDES[dir]) { return PATH_OVERRIDES[dir]; } const sortedPrefixRules = [...PATH_PREFIX_RULES].sort( (a, b) => b.prefix.length - a.prefix.length ); for (const { prefix, category } of sortedPrefixRules) { if (dir.startsWith(prefix) || relativePath.startsWith(prefix)) { return category; } } const topLevel = getTopLevelFolder(relativePath); if (TOP_LEVEL_MAP[topLevel]) { return TOP_LEVEL_MAP[topLevel]; } if (frontmatterCategory) { const key = frontmatterCategory.toLowerCase(); if ((CANONICAL_CATEGORIES as readonly string[]).includes(key)) { return key as SkillCategory; } if (LEGACY_CATEGORY_MAP[key]) { return LEGACY_CATEGORY_MAP[key]; } } return 'meta'; } export function sortCategories(categories: string[]): string[] { const order = new Map(CATEGORY_ORDER.map((c, i) => [c, i])); return [...categories].sort((a, b) => { const ai = order.get(a as SkillCategory) ?? 999; const bi = order.get(b as SkillCategory) ?? 999; if (ai !== bi) return ai - bi; return a.localeCompare(b); }); }