From d7c582b5060246408d6c7408ff961a79c666f979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=98=81=EC=B0=BD=28piknow=29?= Date: Sat, 24 Jan 2026 18:07:10 +0900 Subject: [PATCH] agent: update meta-skill description format to match Anthropic docs Requirement: Current meta-skill description format uses custom `Triggers:` syntax and arbitrary constraints that don't align with official Anthropic documentation. Implementation: - Remove `Triggers:` format, use third-person natural language instead - Update name constraint from max 40 chars to 64 chars (per official docs) - Replace ~100 words limit with "keep concise" guideline - Update validation checklist and anti-patterns sections - Convert skill-template.md to English with new format --- .claude/skills/meta-agent-creator/SKILL.md | 203 ++++++++++++ .../references/agent-templates.md | 259 ++++++++++++++++ .../meta-agent-creator/scripts/init-agent.ts | 168 ++++++++++ .../scripts/validate-agent.ts | 106 +++++++ .claude/skills/meta-skill-creator/SKILL.md | 289 ++++++++++++++++++ .../assets/templates/skill-template.md | 32 ++ .../references/agent-patterns.md | 192 ++++++++++++ .../references/workflows.md | 101 ++++++ .../meta-skill-creator/scripts/init-skill.ts | 243 +++++++++++++++ .../scripts/package-skill.ts | 138 +++++++++ .../scripts/validate-skill.ts | 162 ++++++++++ 11 files changed, 1893 insertions(+) create mode 100644 .claude/skills/meta-agent-creator/SKILL.md create mode 100644 .claude/skills/meta-agent-creator/references/agent-templates.md create mode 100644 .claude/skills/meta-agent-creator/scripts/init-agent.ts create mode 100644 .claude/skills/meta-agent-creator/scripts/validate-agent.ts create mode 100644 .claude/skills/meta-skill-creator/SKILL.md create mode 100644 .claude/skills/meta-skill-creator/assets/templates/skill-template.md create mode 100644 .claude/skills/meta-skill-creator/references/agent-patterns.md create mode 100644 .claude/skills/meta-skill-creator/references/workflows.md create mode 100644 .claude/skills/meta-skill-creator/scripts/init-skill.ts create mode 100644 .claude/skills/meta-skill-creator/scripts/package-skill.ts create mode 100644 .claude/skills/meta-skill-creator/scripts/validate-skill.ts diff --git a/.claude/skills/meta-agent-creator/SKILL.md b/.claude/skills/meta-agent-creator/SKILL.md new file mode 100644 index 0000000..a48d43f --- /dev/null +++ b/.claude/skills/meta-agent-creator/SKILL.md @@ -0,0 +1,203 @@ +--- +name: meta-agent-creator +description: Guide for creating specialized AI agents (subagents). This skill should be used when users want to create a new agent, define agent configurations, or need guidance on agent architecture. +--- + +# Meta-Agent Creator + +A meta-skill that helps create new specialized agents for multi-agent orchestration systems. +Supports Claude Code and OpenCode platforms. + +> **Output Language**: All generated agent content (prompts, descriptions) will be in **English**. + +## Quick Start + +### Create a New Agent + +```bash +# Initialize a new agent +bun scripts/init-agent.ts --path --platform + +# Examples +bun scripts/init-agent.ts security-auditor --path src/agents --platform opencode +bun scripts/init-agent.ts data-analyst --path .claude/agents --platform claude-code +``` + +### Validate Agent + +```bash +bun scripts/validate-agent.ts + +# Examples +bun scripts/validate-agent.ts src/agents/my-agent.ts +bun scripts/validate-agent.ts .claude/agents/my-agent.ts +``` + +## Agent Categories + +Choose the appropriate category based on agent purpose: + +| Category | Purpose | Model Tier | Examples | +| --------------- | ------------------------------- | ---------------- | ----------------- | +| `exploration` | Fast search, codebase discovery | LOW (haiku/fast) | explore, searcher | +| `specialist` | Domain-specific implementation | MEDIUM (sonnet) | frontend, backend | +| `advisor` | Read-only consultation | HIGH (opus) | oracle, architect | +| `utility` | General helpers | LOW (haiku/fast) | writer, formatter | +| `orchestration` | Multi-agent coordination | MEDIUM (sonnet) | orchestrator | + +## Agent Creation Workflow (5 Phases) + +### Phase 1: DEFINE PURPOSE + +**Goal**: Clarify agent's role and responsibilities. + +**Questions to Ask**: + +1. "What specific tasks should this agent handle?" +2. "When should the orchestrator delegate to this agent?" +3. "What domain expertise is required?" +4. "Should it be read-only or have write access?" + +**Deliverables**: + +- Clear role definition +- Trigger conditions for delegation +- Required capabilities list +- Access level decision (readonly vs full) + +### Phase 2: CLASSIFY + +**Goal**: Determine category and model tier. + +**Decision Matrix**: + +| If the agent needs... | Category | Model | Cost | +| ------------------------------- | ------------- | -------------- | --------- | +| Fast codebase search | exploration | haiku/fast | FREE | +| Specific domain implementation | specialist | sonnet/inherit | CHEAP | +| Complex reasoning, architecture | advisor | opus/inherit | EXPENSIVE | +| Simple transformations | utility | haiku/fast | FREE | +| Delegate to other agents | orchestration | sonnet/inherit | CHEAP | + +**Deliverables**: + +- Category assignment +- Model tier selection +- Cost classification + +### Phase 3: DESIGN PROMPT + +**Goal**: Write effective system prompt. + +**Prompt Structure**: + +```markdown +## Role + +[1-2 sentence identity and expertise] + +## Core Capabilities + +- [Capability 1] +- [Capability 2] + +## Workflow + +1. [Step 1] +2. [Step 2] + +## Output Format + +[Structured output definition] + +## Constraints + +- [Constraint 1] +- [Constraint 2] +``` + +**Best Practices**: + +- Keep prompts concise (<500 words) +- Use imperative form +- Include specific output format +- Define clear constraints + +### Phase 4: CONFIGURE + +**Goal**: Create platform-specific configuration. + +#### OpenCode / Claude Code (TypeScript) + +Location: `src/agents/.ts` + +```typescript +export function createMyAgent(model: string): AgentConfig { + return { + name: "my-agent", + description: "Agent description...", + mode: "subagent", + model, + temperature: 0.1, + tools: { include: ["read", "glob", "grep"] }, + prompt: `System prompt...`, + }; +} +``` + +### Phase 5: REGISTER & TEST + +**Goal**: Add agent to registry and verify functionality. + +**Registration Checklist**: + +- [ ] Add to agent definitions/index +- [ ] Update orchestrator's available agents list +- [ ] Add to delegation table if applicable +- [ ] Test with sample delegation + +**Testing**: + +1. Invoke agent with representative task +2. Verify output format matches spec +3. Check tool restrictions are enforced +4. Test edge cases + +## Platform Configurations + +### OpenCode / Claude Code Agent Fields + +| Field | Required | Description | +| ------------- | -------- | ------------------------ | +| `name` | Yes | Agent identifier | +| `description` | Yes | Selection guide | +| `mode` | Yes | Always `"subagent"` | +| `model` | Yes | Model string | +| `temperature` | No | Defaults to 0.1 | +| `tools` | No | Tool whitelist/blacklist | +| `prompt` | Yes | System prompt | + +## Common Agent Templates + +See `references/agent-templates.md` for ready-to-use templates: + +- Exploration Agent +- Verification Agent +- Debugger Agent +- Security Auditor Agent + +## Anti-Patterns to Avoid + +| Anti-Pattern | Problem | Solution | +| ----------------- | -------------------------- | ------------------------- | +| Vague description | Orchestrator can't decide | Include specific triggers | +| Too many tools | Security risk | Minimal tool set | +| Long prompts | Slower, harder to maintain | Keep under 500 words | +| Generic "helper" | No clear purpose | Single responsibility | +| Missing readonly | Security for advisors | Set readonly: true | +| Wrong model tier | Cost or quality issues | Match tier to complexity | + +## References + +- **Agent Templates**: See [references/agent-templates.md](references/agent-templates.md) for ready-to-use templates +- **Agent Patterns**: See [../meta-skill-creator/references/agent-patterns.md](../meta-skill-creator/references/agent-patterns.md) for architecture guidance diff --git a/.claude/skills/meta-agent-creator/references/agent-templates.md b/.claude/skills/meta-agent-creator/references/agent-templates.md new file mode 100644 index 0000000..b2390fb --- /dev/null +++ b/.claude/skills/meta-agent-creator/references/agent-templates.md @@ -0,0 +1,259 @@ +# Agent Templates + +Ready-to-use agent templates for common use cases (OpenCode/Claude Code format). + +## Exploration Agent + +Fast codebase search agent. + +```typescript +export const EXPLORE_METADATA: AgentPromptMetadata = { + category: "exploration", + cost: "FREE", + promptAlias: "탐색기", + triggers: [ + { domain: "탐색", trigger: "코드베이스 구조, 패턴, 스타일 검색" } + ], + useWhen: ["다중 검색 각도 필요", "모듈 구조 파악", "크로스 레이어 패턴 발견"], + avoidWhen: ["검색 대상이 명확할 때", "단일 키워드로 충분할 때"] +}; + +export function createExploreAgent(model: string): AgentConfig { + return { + name: "explore", + description: "코드베이스 탐색 전문가. 'X는 어디에?', 'Y 파일 찾아줘' 질문에 사용.", + mode: "subagent", + model, + temperature: 0.1, + tools: { include: ["read", "glob", "grep", "ast_grep_search"] }, + prompt: `## 역할 + +코드베이스 검색 전문가입니다. + +## 미션 + +다음 질문에 답합니다: +- "X는 어디에 구현되어 있나요?" +- "Y를 포함하는 파일은?" +- "Z를 수행하는 코드를 찾아주세요" + +## 결과 형식 + + +- /절대/경로/파일.ts — 관련 이유 + + + +질문에 대한 직접적인 답변 + + +## 제약 조건 + +- 읽기 전용: 파일 수정 불가 +- 모든 경로는 절대 경로로 반환` + }; +} +``` + +## Verification Agent + +Validates completed work. + +```typescript +export const VERIFIER_METADATA: AgentPromptMetadata = { + category: "utility", + cost: "FREE", + promptAlias: "검증자", + triggers: [ + { domain: "검증", trigger: "작업 완료 주장 시 실제 동작 확인" } + ], + useWhen: ["작업이 완료로 표시된 후", "PR 제출 전 최종 확인"], + avoidWhen: ["작업이 진행 중일 때"] +}; + +export function createVerifierAgent(model: string): AgentConfig { + return { + name: "verifier", + description: "작업 완료 검증 전문가. 완료 주장 시 실제 동작 확인용.", + mode: "subagent", + model, + temperature: 0.1, + tools: { include: ["read", "glob", "grep", "bash"] }, + prompt: `## 역할 + +회의적인 검증자입니다. 완료라고 주장된 작업이 실제로 동작하는지 확인합니다. + +## 검증 절차 + +1. 완료 주장 내용 파악 +2. 구현 존재 및 기능 확인 +3. 관련 테스트 실행 +4. 누락된 엣지 케이스 확인 + +## 보고 형식 + +- 검증 통과 항목 +- 미완료 또는 오류 항목 +- 수정 필요 사항 + +## 제약 조건 + +- 주장을 그대로 믿지 말 것 +- 모든 것을 직접 테스트` + }; +} +``` + +## Debugger Agent + +Root cause analysis specialist. + +```typescript +export const DEBUGGER_METADATA: AgentPromptMetadata = { + category: "specialist", + cost: "CHEAP", + promptAlias: "디버거", + triggers: [ + { domain: "디버깅", trigger: "에러, 테스트 실패 시 근본 원인 분석" } + ], + useWhen: ["에러 발생 시", "테스트 실패 시", "예상치 못한 동작 시"], + avoidWhen: ["단순 타이포 수정", "명확한 문법 오류"] +}; + +export function createDebuggerAgent(model: string): AgentConfig { + return { + name: "debugger", + description: "디버깅 전문가. 에러, 테스트 실패 시 근본 원인 분석.", + mode: "subagent", + model, + temperature: 0.1, + tools: { include: ["read", "glob", "grep", "bash", "lsp_diagnostics"] }, + prompt: `## 역할 + +근본 원인 분석 전문 디버거입니다. + +## 절차 + +1. 에러 메시지와 스택 트레이스 수집 +2. 재현 단계 파악 +3. 실패 위치 격리 +4. 최소한의 수정 구현 +5. 해결 확인 + +## 보고 형식 + +각 이슈에 대해: +- 근본 원인 설명 +- 진단 근거 +- 구체적인 코드 수정 +- 테스트 방법 + +## 제약 조건 + +- 증상이 아닌 원인 수정 +- 최소 변경 원칙` + }; +} +``` + +## Security Auditor Agent + +Security review specialist. + +```typescript +export const SECURITY_AUDITOR_METADATA: AgentPromptMetadata = { + category: "advisor", + cost: "EXPENSIVE", + promptAlias: "보안 감사자", + triggers: [ + { domain: "보안", trigger: "인증, 결제, 민감 데이터 처리 시" } + ], + useWhen: ["인증 코드 작성 시", "결제 로직 구현 시", "민감 데이터 처리 시"], + avoidWhen: ["UI 스타일링", "단순 CRUD"] +}; + +export function createSecurityAuditorAgent(model: string): AgentConfig { + return { + name: "security-auditor", + description: "보안 전문가. 인증, 결제, 민감 데이터 처리 시 사용.", + mode: "subagent", + model, + temperature: 0.1, + tools: { include: ["read", "glob", "grep"] }, + prompt: `## 역할 + +보안 취약점을 감사하는 전문가입니다. + +## 체크리스트 + +호출 시: +1. 보안 관련 코드 경로 식별 +2. 일반적인 취약점 확인 (인젝션, XSS, 인증 우회) +3. 하드코딩된 시크릿 검사 +4. 입력 검증 및 새니타이징 검토 + +## 보고 형식 + +심각도별 보고: +- Critical (배포 전 필수 수정) +- High (조속히 수정) +- Medium (가능할 때 수정) + +## 제약 조건 + +- 읽기 전용 +- 취약점 발견 시 즉시 보고` + }; +} +``` + +## Orchestrator Agent + +Multi-agent coordinator. + +```typescript +export const ORCHESTRATOR_METADATA: AgentPromptMetadata = { + category: "orchestration", + cost: "CHEAP", + promptAlias: "조율자", + triggers: [ + { domain: "조율", trigger: "복잡한 다단계 작업 시" } + ], + useWhen: ["다중 에이전트 조율 필요", "복잡한 작업 분할 필요"], + avoidWhen: ["단순 작업", "단일 도메인 작업"] +}; + +export function createOrchestratorAgent(model: string): AgentConfig { + return { + name: "orchestrator", + description: "다중 에이전트 조율자. 복잡한 다단계 작업 시 사용.", + mode: "subagent", + model, + temperature: 0.1, + tools: { include: ["read", "glob", "grep", "task", "todowrite", "todoread"] }, + prompt: `## 역할 + +다중 에이전트 조율자입니다. + +## 사용 가능한 서브에이전트 + +- explore - 빠른 코드베이스 검색 +- verifier - 작업 완료 검증 +- debugger - 근본 원인 분석 +- security-auditor - 보안 검토 + +## 워크플로우 + +1. 작업 분석 및 분할 +2. 적절한 서브에이전트에 위임 (병렬 가능) +3. 결과 수집 및 검증 +4. 최종 출력 종합 + +## 필수 규칙 + +- 복잡한 작업은 반드시 위임 +- 가능하면 병렬 실행 +- 완료 전 반드시 검증` + }; +} +``` diff --git a/.claude/skills/meta-agent-creator/scripts/init-agent.ts b/.claude/skills/meta-agent-creator/scripts/init-agent.ts new file mode 100644 index 0000000..dc3345b --- /dev/null +++ b/.claude/skills/meta-agent-creator/scripts/init-agent.ts @@ -0,0 +1,168 @@ +#!/usr/bin/env node +/** Agent Initializer - Usage: bun scripts/init-agent.ts --path --platform */ + +import { mkdirSync, writeFileSync, existsSync } from "fs"; +import { join, resolve } from "path"; + +type Platform = "opencode" | "claude-code"; + +const OPENCODE_TEMPLATE = `import type { AgentConfig } from "@opencode-ai/sdk"; +import type { AgentPromptMetadata } from "./types"; + +export const {{CONST_NAME}}_METADATA: AgentPromptMetadata = { + category: "specialist", + cost: "CHEAP", + promptAlias: "{{AGENT_TITLE}}", + triggers: [ + { domain: "[TODO: 도메인]", trigger: "[TODO: 위임 조건]" } + ], + useWhen: [ + "[TODO: 사용 시점 1]", + "[TODO: 사용 시점 2]" + ], + avoidWhen: [ + "[TODO: 피해야 할 시점]" + ] +}; + +export function create{{PASCAL_NAME}}Agent(model: string): AgentConfig { + return { + name: "{{AGENT_NAME}}", + description: "[TODO: 에이전트 설명]", + mode: "subagent", + model, + temperature: 0.1, + tools: { include: ["read", "glob", "grep"] }, + prompt: \`## 역할 + +[TODO: 에이전트의 정체성과 전문 분야] + +## 핵심 역량 + +- [TODO: 역량 1] +- [TODO: 역량 2] + +## 워크플로우 + +호출 시: +1. [TODO: 단계 1] +2. [TODO: 단계 2] + +## 출력 형식 + +[TODO: 구조화된 출력 형식] + +## 제약 조건 + +- [TODO: 제약 1]\` + }; +} +`; + +function toTitleCase(name: string): string { + return name.split("-").map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(" "); +} + +function toPascalCase(name: string): string { + return name.split("-").map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(""); +} + +function toConstCase(name: string): string { + return name.toUpperCase().replace(/-/g, "_"); +} + +function validateAgentName(name: string): { valid: boolean; error?: string } { + if (!name) return { valid: false, error: "Agent name is required" }; + if (name.length > 40) return { valid: false, error: "Name must be 40 characters or less" }; + if (!/^[a-z0-9-]+$/.test(name)) return { valid: false, error: "Only lowercase, numbers, and hyphens allowed" }; + return { valid: true }; +} + +function applyTemplate(template: string, name: string): string { + return template + .replace(/\{\{AGENT_NAME\}\}/g, name) + .replace(/\{\{AGENT_TITLE\}\}/g, toTitleCase(name)) + .replace(/\{\{PASCAL_NAME\}\}/g, toPascalCase(name)) + .replace(/\{\{CONST_NAME\}\}/g, toConstCase(name)); +} + +function initAgent(name: string, basePath: string, platform: Platform): { success: boolean; path?: string; error?: string } { + const validation = validateAgentName(name); + if (!validation.valid) return { success: false, error: validation.error }; + + const outputDir = resolve(basePath); + if (!existsSync(outputDir)) { + mkdirSync(outputDir, { recursive: true }); + } + + try { + let filePath: string; + let content: string; + + filePath = join(outputDir, `${name}.ts`); + content = applyTemplate(OPENCODE_TEMPLATE, name); + + writeFileSync(filePath, content, "utf-8"); + console.log(`Created agent file: ${filePath}`); + console.log("\nNext steps:"); + console.log("1. Complete the [TODO] items"); + console.log("2. Register the agent in your orchestrator"); + console.log("3. Test with sample delegation"); + + return { success: true, path: filePath }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { success: false, error: `File creation error: ${message}` }; + } +} + +function printUsage(): void { + console.log(` +Agent Initializer + +Usage: + bun scripts/init-agent.ts --path --platform + +Arguments: + agent-name Agent name (kebab-case) + --path Output directory + --platform Platform (opencode, claude-code) + +Examples: + bun scripts/init-agent.ts security-auditor --path src/agents --platform opencode + bun scripts/init-agent.ts data-analyst --path .claude/agents --platform claude-code +`); +} + +function main(): void { + const args = process.argv.slice(2); + const pathIndex = args.indexOf("--path"); + const platformIndex = args.indexOf("--platform"); + + if (args.length < 5 || pathIndex === -1 || platformIndex === -1) { + printUsage(); + process.exit(1); + } + + const name = args[0]; + const basePath = args[pathIndex + 1]; + const platform = args[platformIndex + 1] as Platform; + + if (!["opencode", "claude-code"].includes(platform)) { + console.error("Error: Invalid platform. Choose from: opencode, claude-code"); + process.exit(1); + } + + console.log(`Initializing agent: ${name}`); + console.log(`Platform: ${platform}`); + console.log(`Path: ${basePath}\n`); + + const result = initAgent(name, basePath, platform); + + if (!result.success) { + console.error(`Error: ${result.error}`); + process.exit(1); + } +} + +main(); diff --git a/.claude/skills/meta-agent-creator/scripts/validate-agent.ts b/.claude/skills/meta-agent-creator/scripts/validate-agent.ts new file mode 100644 index 0000000..d12a566 --- /dev/null +++ b/.claude/skills/meta-agent-creator/scripts/validate-agent.ts @@ -0,0 +1,106 @@ +#!/usr/bin/env node +/** Agent Validator - Usage: bun scripts/validate-agent.ts */ + +import { existsSync, readFileSync, statSync } from "fs"; +import { extname } from "path"; + +interface ValidationResult { + valid: boolean; + errors: string[]; + warnings: string[]; +} + +function validateOpenCodeAgent(filePath: string): ValidationResult { + const errors: string[] = []; + const warnings: string[] = []; + + const content = readFileSync(filePath, "utf-8"); + + if (!content.includes("export function create")) { + errors.push("Missing agent factory function (export function createXxxAgent)"); + } + + if (!content.includes(": AgentConfig")) { + warnings.push("Missing AgentConfig return type annotation"); + } + + if (!content.includes("name:")) { + errors.push("Missing 'name' field in agent config"); + } + + if (!content.includes("description:")) { + errors.push("Missing 'description' field in agent config"); + } + + if (!content.includes("prompt:")) { + errors.push("Missing 'prompt' field in agent config"); + } + + const todoMatches = content.match(/\[TODO[^\]]*\]/gi); + if (todoMatches && todoMatches.length > 0) { + errors.push(`${todoMatches.length} incomplete [TODO] items found`); + } + + return { valid: errors.length === 0, errors, warnings }; +} + +function validateAgent(filePath: string): ValidationResult { + if (!existsSync(filePath)) { + return { valid: false, errors: [`File not found: ${filePath}`], warnings: [] }; + } + + if (statSync(filePath).isDirectory()) { + return { valid: false, errors: [`Path is a directory, not a file: ${filePath}`], warnings: [] }; + } + + const ext = extname(filePath); + + if (ext === ".ts") { + return validateOpenCodeAgent(filePath); + } else { + return { valid: false, errors: [`Unsupported file type: ${ext} (expected .ts)`], warnings: [] }; + } +} + +function main(): void { + const args = process.argv.slice(2); + + if (args.length < 1) { + console.log("Usage: bun scripts/validate-agent.ts "); + console.log("\nExamples:"); + console.log(" bun scripts/validate-agent.ts src/agents/my-agent.ts"); + console.log(" bun scripts/validate-agent.ts .claude/agents/my-agent.ts"); + process.exit(1); + } + + const filePath = args[0]; + console.log(`Validating agent: ${filePath}\n`); + + const result = validateAgent(filePath); + + if (result.errors.length > 0) { + console.log("Errors:"); + for (const error of result.errors) { + console.log(` - ${error}`); + } + console.log(); + } + + if (result.warnings.length > 0) { + console.log("Warnings:"); + for (const warning of result.warnings) { + console.log(` - ${warning}`); + } + console.log(); + } + + if (result.valid) { + console.log("Agent validation passed"); + process.exit(0); + } else { + console.log("Agent validation failed"); + process.exit(1); + } +} + +main(); diff --git a/.claude/skills/meta-skill-creator/SKILL.md b/.claude/skills/meta-skill-creator/SKILL.md new file mode 100644 index 0000000..752e413 --- /dev/null +++ b/.claude/skills/meta-skill-creator/SKILL.md @@ -0,0 +1,289 @@ +--- +name: meta-skill-creator +description: Guide for creating effective AI agent skills. This skill should be used when users want to create a new skill, update an existing skill, or need guidance on skill architecture and best practices. +--- + +# Meta-Skill Creator + +A meta-skill that helps create new skills for AI agents. This skill provides structured workflows, +automation scripts, and quality assurance mechanisms for skill development. + +> **Output Language**: All generated skill content (SKILL.md, comments, documentation) will be in **English**. + +## Quick Start + +### Create a New Skill + +```bash +# Initialize a new skill (using npx/bunx) +npx ts-node scripts/init-skill.ts --path + +# Or with Bun +bun scripts/init-skill.ts --path + +# Example +bun scripts/init-skill.ts my-awesome-skill --path .claude/skills +``` + +### Validate and Package + +```bash +# Validate skill structure +bun scripts/validate-skill.ts + +# Package for distribution +bun scripts/package-skill.ts [output-dir] +``` + +## Skill Creation Workflow (6 Phases) + +Follow these phases in order. Each phase has specific deliverables and validation criteria. + +### Phase 1: UNDERSTAND + +**Goal**: Gather concrete examples of how the skill will be used. + +**Questions to Ask**: + +1. "What specific scenarios trigger this skill?" +2. "Can you provide 3-5 concrete usage examples?" +3. "What keywords should activate this skill?" +4. "What inputs does it receive? What outputs does it produce?" + +**Deliverables**: + +- Trigger condition list +- 3-5 usage scenarios with examples +- Expected input/output format + +**Exit Criteria**: Clear understanding of skill purpose and usage patterns. + +### Phase 2: PLAN + +**Goal**: Identify reusable content and structure. + +**Analysis Checklist**: + +| Content Type | When to Include | Examples | +| ------------- | ----------------------------------------- | ------------------------------------------- | +| `scripts/` | Repetitive code, deterministic operations | File processing, API calls, automation | +| `references/` | Detailed docs, schemas, lengthy guides | API docs, database schemas, workflow guides | +| `assets/` | Output templates, images, fonts | HTML templates, config files, boilerplate | + +**Freedom Level Decision**: + +| Level | When to Use | Implementation | +| ------ | ---------------------------------------- | ---------------------------------------- | +| High | Multiple valid approaches | Text instructions only | +| Medium | Preferred pattern exists | Pseudocode or parameterized scripts | +| Low | Fragile operations, consistency critical | Specific scripts with minimal parameters | + +**Deliverables**: + +- Directory structure design +- Progressive Disclosure strategy +- Freedom level for each component + +### Phase 3: INITIALIZE + +**Goal**: Create skill directory and template files. + +**Action**: Run the initialization script: + +```bash +bun scripts/init-skill.ts --path +``` + +The script creates: + +``` +skill-name/ +├── SKILL.md # Template with placeholders +├── scripts/ +│ └── example.ts # Example TypeScript script template +├── references/ +│ └── guide.md # Example reference template +└── assets/ + └── .gitkeep # Placeholder for assets +``` + +**Deliverables**: Initialized skill directory with all templates. + +### Phase 4: IMPLEMENT + +**Goal**: Write skill content and implement resources. + +#### 4.1 Write SKILL.md Frontmatter + +```yaml +--- +name: skill-name # Required: kebab-case, max 64 chars +description: What the skill does. This skill should be used when [specific contexts]. +--- +``` + +**Description best practices:** + +- Use third-person format: "This skill should be used when..." +- Include both what the skill does AND when to use it +- Be specific about trigger contexts +- Keep it concise (no strict word limit) + +#### 4.2 Write SKILL.md Body + +Structure options (choose based on skill type): + +| Pattern | Best For | Structure | +| -------------------- | -------------------- | ----------------------------- | +| Workflow-Based | Sequential processes | Decision Tree → Steps | +| Task-Based | Tool collections | Quick Start → Task Categories | +| Reference/Guidelines | Standards, specs | Overview → Guidelines → Specs | +| Capabilities-Based | Integrated systems | Capabilities → Features | + +#### 4.3 Implement Scripts (TypeScript/JavaScript) + +```typescript +#!/usr/bin/env node +// scripts/my-script.ts + +import { readFileSync, writeFileSync } from "fs"; +import { join } from "path"; + +async function main() { + const args = process.argv.slice(2); + // Implementation here + console.log("Script execution complete"); +} + +main().catch(console.error); +``` + +- Test ALL scripts by actually running them +- Include helpful error messages +- Handle edge cases gracefully + +#### 4.4 Write References + +- Keep SKILL.md under 500 lines +- Move detailed content to references/ +- Always link references from SKILL.md + +**Deliverables**: Complete skill implementation. + +### Phase 5: VALIDATE + +**Goal**: Ensure skill meets quality standards. + +**Run Validation**: + +```bash +bun scripts/validate-skill.ts +``` + +**Validation Checklist**: + +| Category | Check | Requirement | +| ----------- | -------------------- | --------------------------------------------- | +| Frontmatter | `name` field | kebab-case, max 64 chars, matches directory | +| Frontmatter | `description` field | Third-person format, what + when to use | +| Structure | SKILL.md exists | Required | +| Structure | Line count | < 500 lines (split to references if exceeded) | +| Content | No TODO placeholders | All [TODO] items resolved | +| Content | No unnecessary files | No README.md, CHANGELOG.md, etc. | +| Scripts | Execution test | All scripts run without errors | +| References | Explicit links | All references linked from SKILL.md | + +**Exit Criteria**: All validation checks pass. + +### Phase 6: PACKAGE + +**Goal**: Create distributable .skill file. + +**Action**: + +```bash +bun scripts/package-skill.ts [output-dir] +``` + +**Output**: `.skill` file (ZIP format with .skill extension) + +**Post-Package**: + +1. Test the packaged skill +2. Collect usage feedback +3. Iterate based on real usage + +## Progressive Disclosure Patterns + +Keep context efficient by loading content progressively: + +| Level | When Loaded | Size Limit | Content | +| ----- | ----------- | ------------ | ------------------------------ | +| 1 | Always | Keep concise | `name` + `description` | +| 2 | On trigger | <500 lines | SKILL.md body | +| 3 | On demand | Unlimited | scripts/, references/, assets/ | + +### Pattern 1: High-level Guide with References + +```markdown +# Main Skill + +## Quick Start + +[Essential instructions here] + +## Advanced Features + +- **Feature A**: See [FEATURE_A.md](references/feature_a.md) +- **Feature B**: See [FEATURE_B.md](references/feature_b.md) +``` + +### Pattern 2: Domain-Specific Organization + +``` +skill/ +├── SKILL.md (overview + navigation) +└── references/ + ├── frontend.md # Frontend-specific + ├── backend.md # Backend-specific + └── deployment.md # Deployment-specific +``` + +### Pattern 3: Conditional Details + +```markdown +## Basic Usage + +[Simple instructions] + +## Advanced (when needed) + +**For complex scenarios**: See [ADVANCED.md](references/advanced.md) +``` + +## Anti-Patterns to Avoid + +| Anti-Pattern | Problem | Solution | +| -------------------- | --------------------- | --------------------------------- | +| Verbose descriptions | Wastes context tokens | Be concise, focus on what + when | +| Vague "when to use" | Skill won't activate | Be specific about usage contexts | +| SKILL.md > 500 lines | Context bloat | Split to references/ | +| Unnecessary files | Clutter and confusion | Only include essential files | +| Untested scripts | Runtime failures | Test all scripts before packaging | +| Deeply nested refs | Hard to navigate | Keep references 1 level deep | +| Duplicated content | Maintenance burden | Single source of truth | + +## Platform Compatibility + +This skill generates output compatible with: + +| Platform | Skill Location | Status | +| ----------- | ------------------- | ------------ | +| Claude Code | `.claude/skills/` | Full Support | +| OpenCode | `.opencode/skills/` | Full Support | + +## References + +- **Workflow Patterns**: See [references/workflows.md](references/workflows.md) for detailed workflow guidance +- **Agent Patterns**: See [references/agent-patterns.md](references/agent-patterns.md) for meta-agent creation +- **Output Templates**: See [assets/templates/](assets/templates/) for templates diff --git a/.claude/skills/meta-skill-creator/assets/templates/skill-template.md b/.claude/skills/meta-skill-creator/assets/templates/skill-template.md new file mode 100644 index 0000000..084aa16 --- /dev/null +++ b/.claude/skills/meta-skill-creator/assets/templates/skill-template.md @@ -0,0 +1,32 @@ +--- +name: {{SKILL_NAME}} +description: {{SKILL_DESCRIPTION}}. This skill should be used when {{WHEN_TO_USE}}. +--- + +# {{SKILL_TITLE}} + +## Overview + +This skill is used for [purpose]. + +## Quick Start + +```bash +bun scripts/example.ts +``` + +## Usage Guide + +### Basic Usage + +[Basic usage instructions] + +### Advanced Features + +[Advanced feature instructions] + +## Resources + +- `scripts/` - Executable scripts +- `references/` - Detailed documentation +- `assets/` - Templates and assets diff --git a/.claude/skills/meta-skill-creator/references/agent-patterns.md b/.claude/skills/meta-skill-creator/references/agent-patterns.md new file mode 100644 index 0000000..34e66df --- /dev/null +++ b/.claude/skills/meta-skill-creator/references/agent-patterns.md @@ -0,0 +1,192 @@ +# Agent Patterns + +Guidance for creating Meta-Agents - agents that help create other agents. + +## Agent Architecture + +### AgentConfig Interface + +```typescript +interface AgentConfig { + name: string; + description: string; + prompt: string; + tools?: string[] | { include?: string[]; exclude?: string[] }; + model?: 'opus' | 'sonnet' | 'haiku' | 'fast' | 'inherit'; + temperature?: number; + readonly?: boolean; + isBackground?: boolean; +} +``` + +### Agent Metadata (for Orchestrators) + +```typescript +interface AgentPromptMetadata { + category: 'exploration' | 'specialist' | 'advisor' | 'utility' | 'orchestration'; + cost: 'FREE' | 'CHEAP' | 'EXPENSIVE'; + triggers: Array<{ domain: string; trigger: string }>; + useWhen?: string[]; + avoidWhen?: string[]; + promptAlias?: string; +} +``` + +## Model Tier Routing + +| Tier | Models | Use Case | Cost | +|------|--------|----------|------| +| HIGH | opus, inherit | Complex analysis, architecture, debugging | EXPENSIVE | +| MEDIUM | sonnet | Standard tasks, moderate complexity | CHEAP | +| LOW | haiku, fast | Simple lookups, fast operations | FREE | + +## Agent Categories + +### Exploration Agents + +Fast, cheap agents for codebase discovery. + +```markdown +--- +name: explore +description: 코드베이스 탐색 전문가. "X는 어디에?", "Y가 있는 파일은?" 질문에 사용 +model: fast +readonly: true +--- + +코드베이스 검색 전문가입니다. 파일과 코드를 찾아 실행 가능한 결과를 반환합니다. + +## 미션 + +다음 질문에 답합니다: +- "X는 어디에 구현되어 있나요?" +- "Y를 포함하는 파일은?" +- "Z를 수행하는 코드를 찾아주세요" + +## 결과 형식 + + + +- /절대/경로/파일1.ts — 관련 이유 +- /절대/경로/파일2.ts — 관련 이유 + + +실제 필요에 대한 직접적인 답변 + + +``` + +### Specialist Agents + +Domain-specific implementation agents. + +```markdown +--- +name: frontend-engineer +description: 프론트엔드 UI/UX 전문가. 시각적 변경, 스타일링, 레이아웃, 애니메이션에 사용 +model: inherit +--- + +프론트엔드 UI/UX 전문 엔지니어입니다. + +## 전문 분야 + +- 컴포넌트 디자인 및 구현 +- 반응형 레이아웃 +- 애니메이션 및 트랜지션 +- 접근성 (a11y) +- 디자인 시스템 준수 +``` + +### Advisor Agents + +Read-only consultation agents for high-stakes decisions. + +```markdown +--- +name: oracle +description: 읽기 전용 자문 에이전트. 아키텍처 결정, 복잡한 디버깅, 2회 이상 실패 후 사용 +model: inherit +readonly: true +--- + +전략적 기술 자문가입니다. 복잡한 분석이나 아키텍처 결정이 필요할 때 호출됩니다. + +## 역할 + +- 코드베이스 분석 +- 구체적이고 구현 가능한 기술 권장 사항 제시 +- 아키텍처 설계 및 리팩토링 로드맵 +- 복잡한 기술 문제 해결 + +## 제약 + +- 읽기 전용: 파일 생성, 수정, 삭제 불가 +- 자문만 제공, 직접 구현하지 않음 +``` + +### Orchestration Agents + +Coordinator agents that delegate to other agents. + +```markdown +--- +name: orchestrator +description: 복잡한 다단계 작업의 마스터 코디네이터. 작업 분할, 위임, 검증 수행 +model: inherit +--- + +다중 에이전트 조율자입니다. + +## 사용 가능한 서브에이전트 + +- `/explore` - 빠른 코드베이스 검색 +- `/librarian` - 문서 및 외부 참조 검색 +- `/oracle` - 아키텍처 자문 +- `/verifier` - 작업 완료 검증 + +## 워크플로우 + +1. 작업 분석 및 분할 +2. 적절한 서브에이전트에 위임 (병렬 가능) +3. 결과 수집 및 검증 +4. 최종 출력 종합 + +## 필수 규칙 + +- 복잡한 작업은 반드시 위임 +- 가능하면 병렬 실행 +- 완료 전 반드시 검증 +``` + +## Platform Format (Claude Code / OpenCode) + +```typescript +export function createExploreAgent(model: string): AgentConfig { + return { + name: "explore", + description: "코드베이스 탐색 전문가...", + mode: "subagent", + model, + temperature: 0.1, + tools: { include: ["read", "glob", "grep", "ast_grep_search"] }, + prompt: `...` + }; +} +``` + +## Best Practices + +### DO + +- Write focused agents with single responsibility +- Include specific trigger conditions in description +- Use readonly for consultation-only agents +- Set appropriate model tier based on complexity + +### DON'T + +- Create vague "helper" agents +- Give broad tool access when not needed +- Use expensive models for simple lookups +- Skip verification in orchestrators diff --git a/.claude/skills/meta-skill-creator/references/workflows.md b/.claude/skills/meta-skill-creator/references/workflows.md new file mode 100644 index 0000000..2584c80 --- /dev/null +++ b/.claude/skills/meta-skill-creator/references/workflows.md @@ -0,0 +1,101 @@ +# Workflow Patterns + +Detailed guidance for structuring skill workflows. + +## Sequential Workflows + +For complex tasks, break operations into clear, sequential steps: + +```markdown +## Workflow + +PDF 폼 작성은 다음 단계를 따릅니다: + +1. 폼 분석 (analyze-form.ts 실행) +2. 필드 매핑 생성 (fields.json 편집) +3. 매핑 검증 (validate-fields.ts 실행) +4. 폼 작성 (fill-form.ts 실행) +5. 출력 검증 (verify-output.ts 실행) +``` + +## Conditional Workflows + +For tasks with branching logic, guide through decision points: + +```markdown +## Workflow Decision Tree + +1. 작업 유형 결정: + **새 콘텐츠 생성?** → "생성 워크플로우" 따르기 + **기존 콘텐츠 수정?** → "편집 워크플로우" 따르기 + +### 생성 워크플로우 +1. 템플릿 선택 +2. 데이터 입력 +3. 검증 및 출력 + +### 편집 워크플로우 +1. 파일 로드 +2. 변경 사항 적용 +3. 백업 후 저장 +``` + +## Iteration Patterns + +For workflows that may need multiple passes: + +```markdown +## 반복 워크플로우 + +1. 초기 구현 수행 +2. 검증 실행 +3. 검증 통과? + - ✅ 예: 완료 + - ❌ 아니오: 오류 수정 후 2단계로 복귀 + +최대 반복 횟수: 3회 (초과 시 사용자에게 알림) +``` + +## Parallel Execution Patterns + +For independent tasks that can run simultaneously: + +```markdown +## 병렬 실행 + +다음 작업들은 동시에 실행 가능합니다: + +| 작업 | 담당 | 상태 | +|------|------|------| +| API 문서 분석 | librarian | 백그라운드 | +| 코드베이스 탐색 | explore | 백그라운드 | +| 유사 구현 검색 | explore | 백그라운드 | + +모든 작업 완료 후 결과 종합 +``` + +## Error Handling Patterns + +```markdown +## 오류 처리 + +### 재시도 가능한 오류 +- 네트워크 타임아웃 → 3회까지 재시도 +- 일시적 API 오류 → 지수 백오프로 재시도 + +### 복구 불가능한 오류 +- 파일 손상 → 사용자에게 알림 후 중단 +- 권한 부족 → 필요한 권한 안내 후 중단 +``` + +## Verification Patterns + +```markdown +## 검증 체크리스트 + +각 작업 완료 후: +- [ ] 출력 파일 존재 확인 +- [ ] 파일 형식 유효성 검증 +- [ ] 예상 결과와 비교 +- [ ] 부작용 없음 확인 +``` diff --git a/.claude/skills/meta-skill-creator/scripts/init-skill.ts b/.claude/skills/meta-skill-creator/scripts/init-skill.ts new file mode 100644 index 0000000..d63ab10 --- /dev/null +++ b/.claude/skills/meta-skill-creator/scripts/init-skill.ts @@ -0,0 +1,243 @@ +#!/usr/bin/env node +/** + * Skill Initializer - Creates a new skill from template + * Usage: bun scripts/init-skill.ts --path + * Example: bun scripts/init-skill.ts my-skill --path .cursor/skills + */ + +import { mkdirSync, writeFileSync, existsSync, chmodSync } from "fs"; +import { join, resolve } from "path"; + +const SKILL_MD_TEMPLATE = `--- +name: {{SKILL_NAME}} +description: | + [TODO: 스킬의 목적과 사용 시점을 명확하게 설명하세요] + [TODO: 트리거 조건을 포함하세요 - 예: "~할 때 사용", "~라고 말하면 활성화"] + 트리거: "키워드1", "키워드2", "keyword3" +--- + +# {{SKILL_TITLE}} + +## 개요 + +[TODO: 이 스킬이 무엇을 하는지 1-2문장으로 설명하세요] + +## 빠른 시작 + +[TODO: 즉시 사용 가능한 예시를 제공하세요] + +\`\`\`bash +# 예시 명령어 +bun scripts/example.ts +\`\`\` + +## 사용 가이드 + +### 기본 사용법 + +[TODO: 기본적인 사용 방법을 설명하세요] + +### 고급 기능 + +[TODO: 필요한 경우 고급 기능을 설명하세요] + +## 리소스 + +### scripts/ + +실행 가능한 스크립트들이 포함됩니다. + +- \`example.ts\` - 예시 스크립트 (필요에 따라 수정하거나 삭제하세요) + +### references/ + +상세 문서가 필요한 경우 이 디렉토리에 추가합니다. + +- \`guide.md\` - 상세 가이드 (필요에 따라 수정하거나 삭제하세요) + +### assets/ + +출력물에 사용되는 파일들이 포함됩니다. + +- 템플릿, 이미지, 설정 파일 등 + +--- + +**불필요한 디렉토리나 파일은 삭제하세요.** 모든 스킬이 세 가지 리소스 유형을 모두 필요로 하지는 않습니다. +`; + +const EXAMPLE_SCRIPT_TEMPLATE = `#!/usr/bin/env node + +async function main(): Promise { + const args = process.argv.slice(2); + console.log("{{SKILL_NAME}} 스크립트 실행 중..."); + console.log("인자:", args); + console.log("✅ 스크립트 실행 완료"); +} + +main().catch((error) => { + console.error("❌ 오류 발생:", error.message); + process.exit(1); +}); +`; + +const REFERENCE_TEMPLATE = `# {{SKILL_TITLE}} - 상세 가이드 + +이 문서는 상세 레퍼런스 문서의 플레이스홀더입니다. +실제 내용으로 교체하거나 필요 없으면 삭제하세요. + +## 레퍼런스 문서가 유용한 경우 + +- 종합적인 API 문서 +- 상세 워크플로우 가이드 +- 복잡한 다단계 프로세스 +- SKILL.md에 담기엔 너무 긴 정보 +- 특정 사용 사례에만 필요한 콘텐츠 + +## 구조 제안 + +### API 레퍼런스 예시 + +- 개요 +- 인증 +- 엔드포인트 및 예시 +- 에러 코드 +- 속도 제한 + +### 워크플로우 가이드 예시 + +- 사전 요구사항 +- 단계별 지침 +- 일반적인 패턴 +- 문제 해결 +- 모범 사례 +`; + +function toTitleCase(skillName: string): string { + return skillName + .split("-") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); +} + +function validateSkillName(name: string): { valid: boolean; error?: string } { + if (!name) { + return { valid: false, error: "스킬 이름이 필요합니다" }; + } + if (name.length > 40) { + return { valid: false, error: "스킬 이름은 40자를 초과할 수 없습니다" }; + } + if (!/^[a-z0-9-]+$/.test(name)) { + return { valid: false, error: "스킬 이름은 소문자, 숫자, 하이픈만 사용할 수 있습니다" }; + } + if (name.startsWith("-") || name.endsWith("-")) { + return { valid: false, error: "스킬 이름은 하이픈으로 시작하거나 끝날 수 없습니다" }; + } + return { valid: true }; +} + +function applyTemplate(template: string, skillName: string): string { + const skillTitle = toTitleCase(skillName); + return template + .replace(/\{\{SKILL_NAME\}\}/g, skillName) + .replace(/\{\{SKILL_TITLE\}\}/g, skillTitle); +} + +interface InitResult { + success: boolean; + path?: string; + error?: string; +} + +function initSkill(skillName: string, basePath: string): InitResult { + const validation = validateSkillName(skillName); + if (!validation.valid) { + return { success: false, error: validation.error }; + } + + const skillDir = resolve(basePath, skillName); + + if (existsSync(skillDir)) { + return { success: false, error: `스킬 디렉토리가 이미 존재합니다: ${skillDir}` }; + } + + try { + mkdirSync(skillDir, { recursive: true }); + mkdirSync(join(skillDir, "scripts"), { recursive: true }); + mkdirSync(join(skillDir, "references"), { recursive: true }); + mkdirSync(join(skillDir, "assets"), { recursive: true }); + console.log(`✅ 스킬 디렉토리 생성됨: ${skillDir}`); + + const skillMdContent = applyTemplate(SKILL_MD_TEMPLATE, skillName); + writeFileSync(join(skillDir, "SKILL.md"), skillMdContent, "utf-8"); + console.log("✅ SKILL.md 생성됨"); + + const scriptContent = applyTemplate(EXAMPLE_SCRIPT_TEMPLATE, skillName); + const scriptPath = join(skillDir, "scripts", "example.ts"); + writeFileSync(scriptPath, scriptContent, "utf-8"); + chmodSync(scriptPath, 0o755); + console.log("✅ scripts/example.ts 생성됨"); + + const refContent = applyTemplate(REFERENCE_TEMPLATE, skillName); + writeFileSync(join(skillDir, "references", "guide.md"), refContent, "utf-8"); + console.log("✅ references/guide.md 생성됨"); + + writeFileSync(join(skillDir, "assets", ".gitkeep"), "", "utf-8"); + console.log("✅ assets/.gitkeep 생성됨"); + + console.log(`\n✅ 스킬 '${skillName}' 초기화 완료: ${skillDir}`); + console.log("\n다음 단계:"); + console.log("1. SKILL.md의 [TODO] 항목들을 완성하세요"); + console.log("2. scripts/, references/, assets/ 내 예시 파일을 수정하거나 삭제하세요"); + console.log("3. 준비가 되면 validate-skill.ts를 실행하여 구조를 검증하세요"); + + return { success: true, path: skillDir }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { success: false, error: `디렉토리 생성 오류: ${message}` }; + } +} + +function printUsage(): void { + console.log(` +스킬 초기화 도구 + +사용법: + bun scripts/init-skill.ts --path + +인자: + skill-name 스킬 이름 (kebab-case, 예: my-awesome-skill) + --path 스킬이 생성될 경로 + +예시: + bun scripts/init-skill.ts my-new-skill --path .claude/skills + bun scripts/init-skill.ts api-helper --path .opencode/skills +`); +} + +function main(): void { + const args = process.argv.slice(2); + const pathIndex = args.indexOf("--path"); + + if (args.length < 3 || pathIndex === -1 || pathIndex + 1 >= args.length) { + printUsage(); + process.exit(1); + } + + const skillName = args[0]; + const basePath = args[pathIndex + 1]; + + console.log(`🚀 스킬 초기화 중: ${skillName}`); + console.log(` 위치: ${basePath}\n`); + + const result = initSkill(skillName, basePath); + + if (result.success) { + process.exit(0); + } else { + console.error(`❌ 오류: ${result.error}`); + process.exit(1); + } +} + +main(); diff --git a/.claude/skills/meta-skill-creator/scripts/package-skill.ts b/.claude/skills/meta-skill-creator/scripts/package-skill.ts new file mode 100644 index 0000000..d8b1dc6 --- /dev/null +++ b/.claude/skills/meta-skill-creator/scripts/package-skill.ts @@ -0,0 +1,138 @@ +#!/usr/bin/env node +/** Skill Packager - Usage: bun scripts/package-skill.ts [output-dir] */ + +import { existsSync, readFileSync, readdirSync, statSync, writeFileSync, mkdirSync } from "fs"; +import { join, basename, resolve } from "path"; +import { execSync } from "child_process"; + +interface PackageResult { + success: boolean; + outputPath?: string; + error?: string; +} + +function getAllFiles(dir: string, baseDir: string = dir): string[] { + const files: string[] = []; + const entries = readdirSync(dir); + + for (const entry of entries) { + const fullPath = join(dir, entry); + const stat = statSync(fullPath); + + if (stat.isDirectory()) { + files.push(...getAllFiles(fullPath, baseDir)); + } else { + files.push(fullPath); + } + } + + return files; +} + +function validateBeforePackage(skillPath: string): { valid: boolean; error?: string } { + const skillMdPath = join(skillPath, "SKILL.md"); + + if (!existsSync(skillMdPath)) { + return { valid: false, error: "SKILL.md 파일이 없습니다" }; + } + + const content = readFileSync(skillMdPath, "utf-8"); + const todoMatches = content.match(/\[TODO[^\]]*\]/gi); + + if (todoMatches && todoMatches.length > 0) { + return { valid: false, error: `완료되지 않은 TODO 항목이 ${todoMatches.length}개 있습니다` }; + } + + const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/); + if (!frontmatterMatch) { + return { valid: false, error: "YAML frontmatter가 없습니다" }; + } + + if (!frontmatterMatch[1].includes("name:")) { + return { valid: false, error: "frontmatter에 'name' 필드가 없습니다" }; + } + + if (!frontmatterMatch[1].includes("description:")) { + return { valid: false, error: "frontmatter에 'description' 필드가 없습니다" }; + } + + return { valid: true }; +} + +function packageSkill(skillPath: string, outputDir?: string): PackageResult { + const resolvedPath = resolve(skillPath); + const skillName = basename(resolvedPath); + + if (!existsSync(resolvedPath)) { + return { success: false, error: `스킬 폴더를 찾을 수 없습니다: ${resolvedPath}` }; + } + + if (!statSync(resolvedPath).isDirectory()) { + return { success: false, error: `경로가 디렉토리가 아닙니다: ${resolvedPath}` }; + } + + console.log("🔍 패키징 전 검증 중..."); + const validation = validateBeforePackage(resolvedPath); + if (!validation.valid) { + return { success: false, error: validation.error }; + } + console.log("✅ 검증 통과\n"); + + const targetDir = outputDir ? resolve(outputDir) : process.cwd(); + if (!existsSync(targetDir)) { + mkdirSync(targetDir, { recursive: true }); + } + + const outputPath = join(targetDir, `${skillName}.skill`); + const files = getAllFiles(resolvedPath); + + console.log("📦 파일 패키징 중:"); + + try { + const zipCommand = `cd "${resolvedPath}" && zip -r "${outputPath}" .`; + execSync(zipCommand, { stdio: "pipe" }); + + for (const file of files) { + const relativePath = file.replace(resolvedPath + "/", ""); + console.log(` - ${relativePath}`); + } + + console.log(`\n✅ 패키징 완료: ${outputPath}`); + return { success: true, outputPath }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { success: false, error: `패키징 오류: ${message}` }; + } +} + +function main(): void { + const args = process.argv.slice(2); + + if (args.length < 1) { + console.log("사용법: bun scripts/package-skill.ts [output-dir]"); + console.log("\n예시:"); + console.log(" bun scripts/package-skill.ts .claude/skills/my-skill"); + console.log(" bun scripts/package-skill.ts .claude/skills/my-skill ./dist"); + process.exit(1); + } + + const skillPath = args[0]; + const outputDir = args[1]; + + console.log(`📦 스킬 패키징: ${skillPath}`); + if (outputDir) { + console.log(` 출력 디렉토리: ${outputDir}`); + } + console.log(); + + const result = packageSkill(skillPath, outputDir); + + if (result.success) { + process.exit(0); + } else { + console.error(`❌ 오류: ${result.error}`); + process.exit(1); + } +} + +main(); diff --git a/.claude/skills/meta-skill-creator/scripts/validate-skill.ts b/.claude/skills/meta-skill-creator/scripts/validate-skill.ts new file mode 100644 index 0000000..e04cb9c --- /dev/null +++ b/.claude/skills/meta-skill-creator/scripts/validate-skill.ts @@ -0,0 +1,162 @@ +#!/usr/bin/env node +/** Skill Validator - Usage: bun scripts/validate-skill.ts */ + +import { existsSync, readFileSync, readdirSync, statSync } from "fs"; +import { join, basename } from "path"; + +interface ValidationResult { + valid: boolean; + errors: string[]; + warnings: string[]; +} + +interface Frontmatter { + name?: string; + description?: string; + [key: string]: unknown; +} + +function parseFrontmatter(content: string): { frontmatter: Frontmatter; body: string } | null { + const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/); + if (!match) return null; + + const yamlContent = match[1]; + const body = match[2]; + const frontmatter: Frontmatter = {}; + + for (const line of yamlContent.split("\n")) { + const colonIndex = line.indexOf(":"); + if (colonIndex > 0) { + const key = line.slice(0, colonIndex).trim(); + const value = line.slice(colonIndex + 1).trim(); + if (key && !key.startsWith(" ")) { + frontmatter[key] = value.replace(/^["']|["']$/g, ""); + } + } + } + + return { frontmatter, body }; +} + +function validateSkill(skillPath: string): ValidationResult { + const errors: string[] = []; + const warnings: string[] = []; + const skillName = basename(skillPath); + + if (!existsSync(skillPath)) { + return { valid: false, errors: [`스킬 폴더를 찾을 수 없습니다: ${skillPath}`], warnings }; + } + + if (!statSync(skillPath).isDirectory()) { + return { valid: false, errors: [`경로가 디렉토리가 아닙니다: ${skillPath}`], warnings }; + } + + const skillMdPath = join(skillPath, "SKILL.md"); + if (!existsSync(skillMdPath)) { + return { valid: false, errors: ["SKILL.md 파일이 없습니다"], warnings }; + } + + const content = readFileSync(skillMdPath, "utf-8"); + const parsed = parseFrontmatter(content); + + if (!parsed) { + errors.push("SKILL.md에 유효한 YAML frontmatter가 없습니다 (--- ... --- 형식 필요)"); + return { valid: false, errors, warnings }; + } + + const { frontmatter, body } = parsed; + + if (!frontmatter.name) { + errors.push("frontmatter에 'name' 필드가 없습니다"); + } else { + const name = String(frontmatter.name); + if (!/^[a-z0-9-]+$/.test(name)) { + errors.push(`'name'이 kebab-case가 아닙니다: ${name}`); + } + if (name.length > 40) { + errors.push(`'name'이 40자를 초과합니다: ${name.length}자`); + } + if (name !== skillName) { + errors.push(`'name'이 디렉토리명과 일치하지 않습니다: ${name} vs ${skillName}`); + } + } + + if (!frontmatter.description) { + errors.push("frontmatter에 'description' 필드가 없습니다"); + } else { + const desc = String(frontmatter.description); + if (desc.length < 20) { + warnings.push("description이 너무 짧습니다 (트리거 조건과 목적을 포함하세요)"); + } + } + + const lines = body.split("\n"); + if (lines.length > 500) { + warnings.push(`SKILL.md가 500줄을 초과합니다 (${lines.length}줄) - references/로 분할을 권장합니다`); + } + + const todoMatches = content.match(/\[TODO[^\]]*\]/gi); + if (todoMatches && todoMatches.length > 0) { + errors.push(`완료되지 않은 TODO 항목이 ${todoMatches.length}개 있습니다`); + } + + const unnecessaryFiles = ["README.md", "CHANGELOG.md", "CONTRIBUTING.md", "LICENSE.md"]; + for (const file of unnecessaryFiles) { + if (existsSync(join(skillPath, file))) { + warnings.push(`불필요한 파일이 있습니다: ${file}`); + } + } + + const refsDir = join(skillPath, "references"); + if (existsSync(refsDir) && statSync(refsDir).isDirectory()) { + const refFiles = readdirSync(refsDir).filter((f) => f.endsWith(".md")); + for (const refFile of refFiles) { + const refPath = `references/${refFile}`; + if (!content.includes(refPath) && !content.includes(refFile)) { + warnings.push(`references/${refFile}이 SKILL.md에서 링크되지 않았습니다`); + } + } + } + + return { valid: errors.length === 0, errors, warnings }; +} + +function main(): void { + const args = process.argv.slice(2); + + if (args.length < 1) { + console.log("사용법: bun scripts/validate-skill.ts "); + process.exit(1); + } + + const skillPath = args[0]; + console.log(`🔍 스킬 검증 중: ${skillPath}\n`); + + const result = validateSkill(skillPath); + + if (result.errors.length > 0) { + console.log("❌ 오류:"); + for (const error of result.errors) { + console.log(` - ${error}`); + } + console.log(); + } + + if (result.warnings.length > 0) { + console.log("⚠️ 경고:"); + for (const warning of result.warnings) { + console.log(` - ${warning}`); + } + console.log(); + } + + if (result.valid) { + console.log("✅ 스킬 검증 통과"); + process.exit(0); + } else { + console.log("❌ 스킬 검증 실패"); + process.exit(1); + } +} + +main();