@katipwork/skills 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/package.json +1 -1
- package/skills/ddd-go-scaffold/SKILL.md +534 -0
- package/skills/engineering/code-review/SKILL.md +0 -20
- package/skills/engineering/code-review/agents/openai.yaml +0 -3
- package/skills/engineering/diagnosing-bugs/SKILL.md +0 -32
- package/skills/engineering/diagnosing-bugs/agents/openai.yaml +0 -3
- package/skills/engineering/tdd/SKILL.md +0 -27
- package/skills/engineering/tdd/agents/openai.yaml +0 -3
- package/skills/productivity/grilling/SKILL.md +0 -25
- package/skills/productivity/grilling/agents/openai.yaml +0 -3
- package/skills/productivity/handoff/SKILL.md +0 -22
- package/skills/productivity/handoff/agents/openai.yaml +0 -3
- package/skills/productivity/prototype/SKILL.md +0 -32
- package/skills/productivity/prototype/agents/openai.yaml +0 -3
package/dist/cli.cjs
CHANGED
|
@@ -139,7 +139,7 @@ async function discoverSkills() {
|
|
|
139
139
|
name: parsed.data.name,
|
|
140
140
|
description: parsed.data.description || "",
|
|
141
141
|
dirPath: fullPath,
|
|
142
|
-
category: category || (0, import_node_path2.basename)((0, import_node_path2.dirname)(fullPath))
|
|
142
|
+
category: category || (dir === skillsRoot ? "general" : (0, import_node_path2.basename)((0, import_node_path2.dirname)(fullPath)))
|
|
143
143
|
});
|
|
144
144
|
}
|
|
145
145
|
} catch {
|
package/dist/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tui.ts","../src/agents.ts","../src/skills.ts","../src/installer.ts","../src/cli-args.ts","../src/cli.ts"],"sourcesContent":["import { search, checkbox, select, confirm } from '@inquirer/prompts';\nimport pc from 'picocolors';\nimport { AGENTS, detectInstalledAgents, type AgentConfig } from './agents.js';\nimport { discoverSkills, type SkillMeta } from './skills.js';\nimport { installSkills, type InstallMode, type InstallResult } from './installer.js';\n\n// ─── simple spinner ────────────────────────────────────────────\n\nfunction spinner(message: string) {\n const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n let i = 0;\n let running = true;\n const timer = setInterval(() => {\n process.stderr.write(`\\r${frames[i++ % frames.length]} ${message}`);\n }, 80);\n return {\n succeed(text: string) {\n clearInterval(timer);\n process.stderr.write(`\\r${pc.green('✓')} ${text}\\n`);\n running = false;\n },\n fail(text: string) {\n clearInterval(timer);\n process.stderr.write(`\\r${pc.red('✗')} ${text}\\n`);\n running = false;\n },\n stop() {\n clearInterval(timer);\n process.stderr.write('\\r' + ' '.repeat(message.length + 4) + '\\r');\n running = false;\n },\n };\n}\n\n// ─── theme helpers ──────────────────────────────────────────────\n\nconst theme = {\n style: {\n highlight: (text: string) => pc.cyan(text),\n },\n};\n\n// ─── steps ──────────────────────────────────────────────────────\n\nasync function selectSkills(skills: SkillMeta[]): Promise<SkillMeta[]> {\n if (skills.length === 0) {\n console.log(pc.yellow('No skills found in skills/ directory.'));\n return [];\n }\n\n const choices = skills.map((s) => ({\n name: `${pc.bold(s.name)} ${pc.dim(`— ${s.description}`)}`,\n value: s,\n }));\n\n // Add a \"select all\" option\n choices.unshift({\n name: pc.italic('Select all skills'),\n value: '__all__' as unknown as SkillMeta,\n });\n\n const selected = await checkbox({\n message: 'Select skills to install:',\n choices,\n pageSize: 15,\n loop: false,\n theme: {\n ...theme,\n helpMode: 'auto',\n },\n });\n\n // Handle \"select all\"\n if (selected.some((s: SkillMeta | string) => (s as unknown as string) === '__all__')) {\n return skills;\n }\n\n return selected as SkillMeta[];\n}\n\nasync function selectAgents(): Promise<AgentConfig[]> {\n const detected = detectInstalledAgents();\n const detectedIds = new Set(detected.map((a) => a.id));\n\n const choices = AGENTS.map((a) => ({\n name: detectedIds.has(a.id)\n ? `${a.displayName} ${pc.green('(detected)')}`\n : a.displayName,\n value: a,\n checked: detectedIds.has(a.id),\n }));\n\n return checkbox({\n message: 'Select target agents:',\n choices,\n pageSize: 12,\n loop: false,\n theme: {\n ...theme,\n helpMode: 'auto',\n },\n validate: (answer) => {\n if (answer.length === 0) return 'Select at least one agent.';\n return true;\n },\n });\n}\n\nasync function selectScope(): Promise<boolean> {\n return select({\n message: 'Install scope:',\n choices: [\n {\n name: `${pc.bold('Project')} — install to ./<agent>/skills/ (shared with your team)`,\n value: false,\n },\n {\n name: `${pc.bold('Global')} — install to ~/<agent>/skills/ (available everywhere)`,\n value: true,\n },\n ],\n default: false,\n });\n}\n\nasync function selectMode(): Promise<InstallMode> {\n return select({\n message: 'Install method:',\n choices: [\n {\n name: `${pc.bold('Symlink')} ${pc.green('(recommended)')} — single source of truth at .agents/skills/, symlinked to agent dirs. Easy updates.`,\n value: 'symlink',\n },\n {\n name: `${pc.bold('Copy')} — independent copies for each agent. Use when symlinks aren't supported.`,\n value: 'copy',\n },\n ],\n default: 'symlink',\n });\n}\n\nasync function confirmInstallation(\n skills: SkillMeta[],\n agents: AgentConfig[],\n global: boolean,\n mode: InstallMode,\n): Promise<boolean> {\n console.log('\\n' + pc.cyan('─── Installation summary ───'));\n console.log(pc.white(` Skills: ${pc.bold(skills.map((s) => s.name).join(', '))}`));\n console.log(pc.white(` Agents: ${pc.bold(agents.map((a) => a.displayName).join(', '))}`));\n console.log(pc.white(` Scope: ${pc.bold(global ? 'Global' : 'Project')}`));\n console.log(pc.white(` Method: ${pc.bold(mode === 'symlink' ? 'Symlink (canonical at .agents/skills/)' : 'Copy')}`));\n console.log('');\n\n return confirm({\n message: 'Proceed with installation?',\n default: true,\n });\n}\n\nfunction printResults(results: InstallResult[]): void {\n const succeeded = results.filter((r) => r.success);\n const failed = results.filter((r) => !r.success);\n\n console.log('');\n if (succeeded.length > 0) {\n console.log(pc.green(`✓ ${succeeded.length} skill(s) installed successfully:`));\n for (const r of succeeded) {\n console.log(pc.dim(` ${r.skill} → ${r.agent} (${r.mode}) → ${r.path}`));\n }\n }\n if (failed.length > 0) {\n console.log(pc.red(`✗ ${failed.length} skill(s) failed:`));\n for (const r of failed) {\n console.log(pc.red(` ${r.skill} → ${r.agent}: ${r.error}`));\n }\n }\n\n // Post-install hints\n if (succeeded.length > 0) {\n console.log('');\n console.log(pc.cyan('Tip:'));\n console.log(pc.dim(' Skills are installed. Restart your agent for changes to take effect.'));\n console.log(pc.dim(' To update, re-run this installer or git pull + re-link.'));\n }\n}\n\n// ─── main ───────────────────────────────────────────────────────\n\nexport async function runTui(): Promise<void> {\n console.log('');\n console.log(pc.bold(pc.cyan('@katipwork/skills')));\n console.log(pc.dim('Curated agent skills installer'));\n console.log('');\n\n // 1. Discover skills\n const discoverSpinner = spinner('Discovering skills...');\n let skills: SkillMeta[];\n try {\n skills = await discoverSkills();\n discoverSpinner.succeed(`Found ${skills.length} skill(s).`);\n } catch (err) {\n discoverSpinner.fail('Failed to discover skills.');\n console.error(err);\n process.exit(1);\n }\n\n if (skills.length === 0) {\n console.log(pc.yellow('No skills found. Add SKILL.md files under skills/ and try again.'));\n process.exit(0);\n }\n\n // 2. Select skills\n const selectedSkills = await selectSkills(skills);\n if (selectedSkills.length === 0) {\n console.log(pc.yellow('No skills selected. Exiting.'));\n process.exit(0);\n }\n\n // 3. Select agents\n const selectedAgents = await selectAgents();\n\n // 4. Scope\n const global = await selectScope();\n\n // 5. Mode\n const mode = await selectMode();\n\n // 6. Confirm\n const confirmed = await confirmInstallation(selectedSkills, selectedAgents, global, mode);\n if (!confirmed) {\n console.log(pc.yellow('Installation cancelled.'));\n process.exit(0);\n }\n\n // 7. Install\n const installSpinner = spinner('Installing skills...');\n let results: InstallResult[];\n try {\n results = await installSkills(selectedSkills, selectedAgents, { global, mode });\n installSpinner.stop();\n } catch (err) {\n installSpinner.fail('Installation failed.');\n console.error(err);\n process.exit(1);\n }\n\n printResults(results);\n\n if (results.some((r) => !r.success)) {\n process.exit(1);\n }\n}\n","import { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { existsSync } from 'node:fs';\n\nconst home = homedir();\n\nexport interface AgentConfig {\n /** CLI flag value, e.g. \"claude-code\" */\n id: string;\n /** Human-readable label */\n displayName: string;\n /** Relative path from project root for project-scoped installs */\n projectSkillsDir: string;\n /** Absolute path for global installs */\n globalSkillsDir: string;\n /** Quick check if agent is installed */\n detectInstalled: () => boolean;\n}\n\nexport type AgentId = (typeof AGENTS)[number]['id'];\n\n/**\n * All supported coding agents.\n * Each has a project-skills dir (relative to CWD) and a global-skills dir (absolute).\n */\nexport const AGENTS: AgentConfig[] = [\n {\n id: 'pi',\n displayName: 'Pi',\n projectSkillsDir: '.pi/skills',\n globalSkillsDir: join(home, '.pi/agent/skills'),\n detectInstalled: () => existsSync(join(home, '.pi/agent')),\n },\n {\n id: 'claude-code',\n displayName: 'Claude Code',\n projectSkillsDir: '.claude/skills',\n globalSkillsDir: join(home, '.claude/skills'),\n detectInstalled: () => existsSync(join(home, '.claude')),\n },\n {\n id: 'codex',\n displayName: 'Codex',\n projectSkillsDir: '.agents/skills',\n globalSkillsDir: join(home, '.codex/skills'),\n detectInstalled: () => existsSync(join(home, '.codex')),\n },\n {\n id: 'github-copilot',\n displayName: 'GitHub Copilot',\n projectSkillsDir: '.agents/skills',\n globalSkillsDir: join(home, '.copilot/skills'),\n detectInstalled: () => existsSync(join(home, '.copilot')),\n },\n {\n id: 'hermes-agent',\n displayName: 'Hermes Agent',\n projectSkillsDir: '.hermes/skills',\n globalSkillsDir: join(home, '.hermes/skills'),\n detectInstalled: () => existsSync(join(home, '.hermes')),\n },\n {\n id: 'cursor',\n displayName: 'Cursor',\n projectSkillsDir: '.agents/skills',\n globalSkillsDir: join(home, '.cursor/skills'),\n detectInstalled: () => existsSync(join(home, '.cursor')),\n },\n {\n id: 'opencode',\n displayName: 'OpenCode',\n projectSkillsDir: '.agents/skills',\n globalSkillsDir: join(home, '.config/opencode/skills'),\n detectInstalled: () => existsSync(join(home, '.config/opencode')),\n },\n];\n\n/** Lookup agent config by id */\nexport function getAgent(id: string): AgentConfig | undefined {\n return AGENTS.find((a) => a.id === id);\n}\n\n/** Detect which agents are installed on this machine */\nexport function detectInstalledAgents(): AgentConfig[] {\n return AGENTS.filter((a) => a.detectInstalled());\n}\n","import { readFileSync, existsSync } from 'node:fs';\nimport { readdir } from 'node:fs/promises';\nimport { join, basename, dirname } from 'node:path';\n\n// In CJS, __dirname is a global pointing to the directory of the current file.\n// After build, __dirname is dist/ and skills/ is at ../skills.\ndeclare const __dirname: string;\n\nexport interface SkillMeta {\n /** Skill name from SKILL.md frontmatter */\n name: string;\n /** Description from SKILL.md frontmatter */\n description: string;\n /** Path to the skill directory on disk */\n dirPath: string;\n /** Which category/bucket the skill lives in, e.g. \"engineering\" */\n category: string;\n}\n\n/**\n * Parse YAML frontmatter from a SKILL.md file.\n * Minimal parser that handles `---` delimited frontmatter.\n */\nfunction parseFrontmatter(content: string): { data: Record<string, string> } | null {\n const match = content.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---/);\n if (!match) return null;\n\n const data: Record<string, string> = {};\n for (const line of match[1].split('\\n')) {\n const colonIdx = line.indexOf(':');\n if (colonIdx === -1) continue;\n const key = line.slice(0, colonIdx).trim();\n const value = line.slice(colonIdx + 1).trim();\n if (key && value) data[key] = value;\n }\n return { data };\n}\n\n/**\n * Find the skills/ directory.\n * When built: dist/ → ../skills\n * When running from source (tsx): src/ → ../skills\n * When installed globally: <package>/dist/ → ../skills\n */\nfunction getSkillsRoot(): string {\n // dist/ → ../skills (bundled case)\n const bundledPath = join(__dirname, '..', 'skills');\n if (existsSync(bundledPath)) return bundledPath;\n\n // CWD fallback\n return join(process.cwd(), 'skills');\n}\n\n/**\n * Discover all skills under skills/.\n * Skills can be at skills/<name>/SKILL.md or skills/<category>/<name>/SKILL.md.\n */\nexport async function discoverSkills(): Promise<SkillMeta[]> {\n const skillsRoot = getSkillsRoot();\n const skills: SkillMeta[] = [];\n\n if (!existsSync(skillsRoot)) {\n return skills;\n }\n\n async function walk(dir: string, category: string = ''): Promise<void> {\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;\n\n const fullPath = join(dir, entry.name);\n const skillMdPath = join(fullPath, 'SKILL.md');\n\n if (existsSync(skillMdPath)) {\n // This is a skill directory (has SKILL.md)\n try {\n const content = readFileSync(skillMdPath, 'utf-8');\n const parsed = parseFrontmatter(content);\n if (parsed && parsed.data.name) {\n skills.push({\n name: parsed.data.name,\n description: parsed.data.description || '',\n dirPath: fullPath,\n category: category || basename(dirname(fullPath)),\n });\n }\n } catch {\n // skip broken skills\n }\n } else {\n // Recurse into subdirectories (category level)\n const subCategory = category ? `${category}/${entry.name}` : entry.name;\n await walk(fullPath, category || entry.name);\n }\n }\n }\n\n await walk(skillsRoot);\n return skills.sort((a, b) => a.name.localeCompare(b.name));\n}\n","import { existsSync } from 'node:fs';\nimport {\n mkdir,\n cp,\n symlink,\n rm,\n readlink,\n lstat,\n realpath,\n stat,\n} from 'node:fs/promises';\nimport { join, relative, dirname, resolve, basename } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { AgentConfig } from './agents.js';\nimport type { SkillMeta } from './skills.js';\n\nconst home = homedir();\n\nexport type InstallMode = 'symlink' | 'copy';\n\nexport interface InstallResult {\n skill: string;\n agent: string;\n success: boolean;\n path: string;\n mode: InstallMode;\n error?: string;\n}\n\n/** Canonical skills directory: .agents/skills/ */\nexport function getCanonicalDir(global: boolean, cwd?: string): string {\n const base = global ? home : cwd || process.cwd();\n return join(base, '.agents', 'skills');\n}\n\nfunction getAgentSkillsDir(agent: AgentConfig, global: boolean, cwd?: string): string {\n const base = global ? home : cwd || process.cwd();\n return global ? agent.globalSkillsDir : join(base, agent.projectSkillsDir);\n}\n\n/**\n * Resolve a symlink's parent directory to its real path.\n * Handles the case where e.g. ~/.claude/skills is a symlink to ~/.agents/skills.\n */\nasync function resolveParentSymlinks(p: string): Promise<string> {\n const d = dirname(p);\n const b = basename(p);\n try {\n return join(await realpath(d), b);\n } catch {\n return p;\n }\n}\n\n/**\n * Create a symlink at `linkPath` pointing to `target`.\n * Returns true on success, false if fallback to copy is needed.\n */\nasync function createSymlink(target: string, linkPath: string): Promise<boolean> {\n try {\n const resolvedTarget = resolve(target);\n const resolvedLinkPath = resolve(linkPath);\n\n // If they resolve to the same physical path, skip\n try {\n const [realTrg, realLnk] = await Promise.all([\n realpath(resolvedTarget),\n realpath(resolvedLinkPath),\n ]);\n if (realTrg === realLnk) return true;\n } catch {\n // paths may not exist yet — continue\n }\n\n // Check parent symlinks\n const realTgtWithParent = await resolveParentSymlinks(target);\n const realLnkWithParent = await resolveParentSymlinks(linkPath);\n if (realTgtWithParent === realLnkWithParent) return true;\n\n // Remove existing link/dir at target\n try {\n const stats = await lstat(linkPath);\n if (stats.isSymbolicLink()) {\n const existing = await readlink(linkPath);\n if (resolve(dirname(linkPath), existing) === resolvedTarget) return true;\n await rm(linkPath);\n } else {\n await rm(linkPath, { recursive: true });\n }\n } catch {\n // Doesn't exist — fine\n }\n\n const linkDir = dirname(linkPath);\n await mkdir(linkDir, { recursive: true });\n\n const realLinkDir = await resolveParentSymlinks(linkDir);\n const relPath = relative(realLinkDir, target);\n\n await symlink(relPath, linkPath);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Recursively copy a directory, skipping .git and node_modules.\n */\nasync function copyDir(src: string, dest: string): Promise<void> {\n await mkdir(dest, { recursive: true });\n\n // Use cp with recursive\n await cp(src, dest, {\n recursive: true,\n dereference: true,\n filter: (srcPath) => {\n const name = basename(srcPath);\n return name !== '.git' && name !== 'node_modules';\n },\n });\n}\n\n/**\n * Install a single skill for a single agent.\n */\nexport async function installSkill(\n skill: SkillMeta,\n agent: AgentConfig,\n options: {\n global?: boolean;\n cwd?: string;\n mode?: InstallMode;\n } = {},\n): Promise<InstallResult> {\n const isGlobal = options.global ?? false;\n const cwd = options.cwd ?? process.cwd();\n const mode = options.mode ?? 'symlink';\n\n const skillName = skill.name;\n const canonicalDir = join(getCanonicalDir(isGlobal, cwd), skillName);\n const agentDir = join(getAgentSkillsDir(agent, isGlobal, cwd), skillName);\n\n try {\n if (mode === 'copy') {\n // Copy directly to agent directory\n await rm(agentDir, { recursive: true, force: true });\n await copyDir(skill.dirPath, agentDir);\n return { skill: skillName, agent: agent.id, success: true, path: agentDir, mode: 'copy' };\n }\n\n // Symlink mode: copy to canonical, then symlink from agent dir\n await rm(canonicalDir, { recursive: true, force: true });\n await copyDir(skill.dirPath, canonicalDir);\n\n // For agents that share the universal .agents/skills dir, canonical is already correct\n if (agent.projectSkillsDir === '.agents/skills' && !isGlobal) {\n // Already installed at canonical location, nothing to symlink\n return {\n skill: skillName,\n agent: agent.id,\n success: true,\n path: canonicalDir,\n mode: 'symlink',\n };\n }\n\n const ok = await createSymlink(canonicalDir, agentDir);\n if (!ok) {\n // Fallback to copy\n await rm(agentDir, { recursive: true, force: true });\n await copyDir(skill.dirPath, agentDir);\n }\n\n return {\n skill: skillName,\n agent: agent.id,\n success: true,\n path: ok ? agentDir : agentDir,\n mode: 'symlink',\n };\n } catch (err) {\n return {\n skill: skillName,\n agent: agent.id,\n success: false,\n path: agentDir,\n mode,\n error: err instanceof Error ? err.message : 'Unknown error',\n };\n }\n}\n\n/**\n * Install multiple skills for multiple agents.\n */\nexport async function installSkills(\n skills: SkillMeta[],\n agents: AgentConfig[],\n options: {\n global?: boolean;\n cwd?: string;\n mode?: InstallMode;\n } = {},\n): Promise<InstallResult[]> {\n const results: InstallResult[] = [];\n for (const skill of skills) {\n for (const agent of agents) {\n results.push(await installSkill(skill, agent, options));\n }\n }\n return results;\n}\n","import pc from 'picocolors';\nimport { AGENTS, getAgent } from './agents.js';\nimport { discoverSkills } from './skills.js';\nimport { installSkills } from './installer.js';\n\nfunction showHelp(): void {\n console.log(`\n${pc.bold(pc.cyan('@katipwork/skills'))} — Curated agent skills installer\n\n${pc.bold('Usage:')}\n npx @katipwork/skills Interactive TUI (default)\n npx @katipwork/skills --list List available skills\n npx @katipwork/skills --install <skills...> [options]\n\n${pc.bold('Options:')}\n --help, -h Show this help\n --list, -l List available skills and exit\n --install, -i Install specific skills (followed by skill names)\n --agent, -a Target agent(s): pi, claude-code, codex, github-copilot,\n hermes-agent, cursor, opencode (can repeat)\n --all-agents Install to all detected agents\n --global, -g Install globally (~/<agent>/skills/)\n --copy Copy files instead of symlinking\n --yes, -y Skip confirmation prompt\n\n${pc.bold('Examples:')}\n npx @katipwork/skills\n npx @katipwork/skills --list\n npx @katipwork/skills -i code-review tdd -a pi -a claude-code -y\n npx @katipwork/skills -i '*' --all-agents --global\n\n${pc.bold('Agents:')}\n ${AGENTS.map((a) => `${a.id} (${a.displayName})`).join(', ')}\n\n${pc.dim('Source: https://github.com/katipwork/skills')}\n`);\n}\n\ninterface ParsedArgs {\n help: boolean;\n list: boolean;\n install: string[];\n agents: string[];\n allAgents: boolean;\n global: boolean;\n copy: boolean;\n yes: boolean;\n}\n\nfunction parseArgs(args: string[]): ParsedArgs {\n const result: ParsedArgs = {\n help: false,\n list: false,\n install: [],\n agents: [],\n allAgents: false,\n global: false,\n copy: false,\n yes: false,\n };\n\n let i = 0;\n while (i < args.length) {\n const arg = args[i];\n switch (arg) {\n case '--help':\n case '-h':\n result.help = true;\n break;\n case '--list':\n case '-l':\n result.list = true;\n break;\n case '--install':\n case '-i':\n i++;\n while (i < args.length && !args[i].startsWith('-')) {\n result.install.push(args[i]);\n i++;\n }\n i--; // compensate for loop increment\n break;\n case '--agent':\n case '-a':\n i++;\n while (i < args.length && !args[i].startsWith('-')) {\n result.agents.push(args[i]);\n i++;\n }\n i--;\n break;\n case '--all-agents':\n result.allAgents = true;\n break;\n case '--global':\n case '-g':\n result.global = true;\n break;\n case '--copy':\n result.copy = true;\n break;\n case '--yes':\n case '-y':\n result.yes = true;\n break;\n default:\n // Treat as skill name if not a flag\n if (!arg.startsWith('-')) {\n result.install.push(arg);\n }\n }\n i++;\n }\n\n return result;\n}\n\nexport async function runCli(rawArgs: string[]): Promise<void> {\n const args = parseArgs(rawArgs);\n\n if (args.help) {\n showHelp();\n process.exit(0);\n }\n\n if (args.list) {\n const skills = await discoverSkills();\n if (skills.length === 0) {\n console.log(pc.yellow('No skills found.'));\n } else {\n console.log(pc.bold(`Available skills (${skills.length}):\\n`));\n for (const s of skills) {\n console.log(` ${pc.bold(pc.cyan(s.name))}`);\n console.log(` ${pc.dim(s.description)}`);\n console.log(` category: ${s.category}`);\n console.log('');\n }\n }\n process.exit(0);\n }\n\n // Install mode\n if (args.install.length > 0) {\n const allSkills = await discoverSkills();\n let skillsToInstall = allSkills;\n\n // Filter by requested names unless '*' is specified\n if (!args.install.includes('*')) {\n skillsToInstall = allSkills.filter((s) =>\n args.install.some((name) => s.name === name),\n );\n const notFound = args.install.filter(\n (name) => !allSkills.some((s) => s.name === name),\n );\n if (notFound.length > 0) {\n console.log(pc.yellow(`Warning: skills not found: ${notFound.join(', ')}`));\n }\n }\n\n if (skillsToInstall.length === 0) {\n console.log(pc.red('No matching skills found to install.'));\n process.exit(1);\n }\n\n // Resolve agents\n let agentsToUse = args.agents\n .map((id) => getAgent(id))\n .filter((a): a is NonNullable<typeof a> => !!a);\n\n if (args.allAgents) {\n // Use all agents\n agentsToUse = AGENTS;\n }\n\n if (agentsToUse.length === 0) {\n console.log(pc.red('No agents specified. Use --agent <id> or --all-agents.'));\n process.exit(1);\n }\n\n const mode = args.copy ? 'copy' : 'symlink';\n\n console.log(pc.cyan('\\n─── Installing ───'));\n console.log(` Skills: ${pc.bold(skillsToInstall.map((s) => s.name).join(', '))}`);\n console.log(` Agents: ${pc.bold(agentsToUse.map((a) => a.displayName).join(', '))}`);\n console.log(` Scope: ${pc.bold(args.global ? 'Global' : 'Project')}`);\n console.log(` Method: ${pc.bold(mode)}`);\n console.log('');\n\n const results = await installSkills(skillsToInstall, agentsToUse, {\n global: args.global,\n mode,\n });\n\n const succeeded = results.filter((r) => r.success);\n const failed = results.filter((r) => !r.success);\n\n if (succeeded.length > 0) {\n console.log(pc.green(`✓ ${succeeded.length} installed successfully`));\n }\n if (failed.length > 0) {\n console.log(pc.red(`✗ ${failed.length} failed`));\n for (const r of failed) {\n console.log(pc.red(` ${r.skill} → ${r.agent}: ${r.error}`));\n }\n process.exit(1);\n }\n process.exit(0);\n }\n\n // No recognized mode — show help\n showHelp();\n}\n","// Shebang is added by tsup banner at build time.\nimport { runTui } from './tui.js';\nimport { runCli } from './cli-args.js';\n\n// If CLI arguments are provided (beyond just the binary), run in non-interactive mode.\n// Otherwise, launch the TUI.\nconst args = process.argv.slice(2);\n\nif (args.length > 0) {\n runCli(args).catch((err) => {\n console.error('Error:', err.message);\n process.exit(1);\n });\n} else {\n runTui().catch((err) => {\n console.error('Unexpected error:', err);\n process.exit(1);\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,qBAAkD;AAClD,wBAAe;;;ACDf,qBAAwB;AACxB,uBAAqB;AACrB,qBAA2B;AAE3B,IAAM,WAAO,wBAAQ;AAqBd,IAAM,SAAwB;AAAA,EACnC;AAAA,IACE,IAAI;AAAA,IACJ,aAAa;AAAA,IACb,kBAAkB;AAAA,IAClB,qBAAiB,uBAAK,MAAM,kBAAkB;AAAA,IAC9C,iBAAiB,UAAM,+BAAW,uBAAK,MAAM,WAAW,CAAC;AAAA,EAC3D;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,aAAa;AAAA,IACb,kBAAkB;AAAA,IAClB,qBAAiB,uBAAK,MAAM,gBAAgB;AAAA,IAC5C,iBAAiB,UAAM,+BAAW,uBAAK,MAAM,SAAS,CAAC;AAAA,EACzD;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,aAAa;AAAA,IACb,kBAAkB;AAAA,IAClB,qBAAiB,uBAAK,MAAM,eAAe;AAAA,IAC3C,iBAAiB,UAAM,+BAAW,uBAAK,MAAM,QAAQ,CAAC;AAAA,EACxD;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,aAAa;AAAA,IACb,kBAAkB;AAAA,IAClB,qBAAiB,uBAAK,MAAM,iBAAiB;AAAA,IAC7C,iBAAiB,UAAM,+BAAW,uBAAK,MAAM,UAAU,CAAC;AAAA,EAC1D;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,aAAa;AAAA,IACb,kBAAkB;AAAA,IAClB,qBAAiB,uBAAK,MAAM,gBAAgB;AAAA,IAC5C,iBAAiB,UAAM,+BAAW,uBAAK,MAAM,SAAS,CAAC;AAAA,EACzD;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,aAAa;AAAA,IACb,kBAAkB;AAAA,IAClB,qBAAiB,uBAAK,MAAM,gBAAgB;AAAA,IAC5C,iBAAiB,UAAM,+BAAW,uBAAK,MAAM,SAAS,CAAC;AAAA,EACzD;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,aAAa;AAAA,IACb,kBAAkB;AAAA,IAClB,qBAAiB,uBAAK,MAAM,yBAAyB;AAAA,IACrD,iBAAiB,UAAM,+BAAW,uBAAK,MAAM,kBAAkB,CAAC;AAAA,EAClE;AACF;AAGO,SAAS,SAAS,IAAqC;AAC5D,SAAO,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AACvC;AAGO,SAAS,wBAAuC;AACrD,SAAO,OAAO,OAAO,CAAC,MAAM,EAAE,gBAAgB,CAAC;AACjD;;;ACrFA,IAAAA,kBAAyC;AACzC,sBAAwB;AACxB,IAAAC,oBAAwC;AAqBxC,SAAS,iBAAiB,SAA0D;AAClF,QAAM,QAAQ,QAAQ,MAAM,6BAA6B;AACzD,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,OAA+B,CAAC;AACtC,aAAW,QAAQ,MAAM,CAAC,EAAE,MAAM,IAAI,GAAG;AACvC,UAAM,WAAW,KAAK,QAAQ,GAAG;AACjC,QAAI,aAAa,GAAI;AACrB,UAAM,MAAM,KAAK,MAAM,GAAG,QAAQ,EAAE,KAAK;AACzC,UAAM,QAAQ,KAAK,MAAM,WAAW,CAAC,EAAE,KAAK;AAC5C,QAAI,OAAO,MAAO,MAAK,GAAG,IAAI;AAAA,EAChC;AACA,SAAO,EAAE,KAAK;AAChB;AAQA,SAAS,gBAAwB;AAE/B,QAAM,kBAAc,wBAAK,WAAW,MAAM,QAAQ;AAClD,UAAI,4BAAW,WAAW,EAAG,QAAO;AAGpC,aAAO,wBAAK,QAAQ,IAAI,GAAG,QAAQ;AACrC;AAMA,eAAsB,iBAAuC;AAC3D,QAAM,aAAa,cAAc;AACjC,QAAM,SAAsB,CAAC;AAE7B,MAAI,KAAC,4BAAW,UAAU,GAAG;AAC3B,WAAO;AAAA,EACT;AAEA,iBAAe,KAAK,KAAa,WAAmB,IAAmB;AACrE,QAAI;AACJ,QAAI;AACF,gBAAU,UAAM,yBAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,IACtD,QAAQ;AACN;AAAA,IACF;AAEA,eAAW,SAAS,SAAS;AAC3B,UAAI,CAAC,MAAM,YAAY,EAAG;AAC1B,UAAI,MAAM,KAAK,WAAW,GAAG,KAAK,MAAM,SAAS,eAAgB;AAEjE,YAAM,eAAW,wBAAK,KAAK,MAAM,IAAI;AACrC,YAAM,kBAAc,wBAAK,UAAU,UAAU;AAE7C,cAAI,4BAAW,WAAW,GAAG;AAE3B,YAAI;AACF,gBAAM,cAAU,8BAAa,aAAa,OAAO;AACjD,gBAAM,SAAS,iBAAiB,OAAO;AACvC,cAAI,UAAU,OAAO,KAAK,MAAM;AAC9B,mBAAO,KAAK;AAAA,cACV,MAAM,OAAO,KAAK;AAAA,cAClB,aAAa,OAAO,KAAK,eAAe;AAAA,cACxC,SAAS;AAAA,cACT,UAAU,gBAAY,gCAAS,2BAAQ,QAAQ,CAAC;AAAA,YAClD,CAAC;AAAA,UACH;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF,OAAO;AAEL,cAAM,cAAc,WAAW,GAAG,QAAQ,IAAI,MAAM,IAAI,KAAK,MAAM;AACnE,cAAM,KAAK,UAAU,YAAY,MAAM,IAAI;AAAA,MAC7C;AAAA,IACF;AAAA,EACF;AAEA,QAAM,KAAK,UAAU;AACrB,SAAO,OAAO,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAC3D;;;ACzGA,IAAAC,mBASO;AACP,IAAAC,oBAA2D;AAC3D,IAAAC,kBAAwB;AAIxB,IAAMC,YAAO,yBAAQ;AAcd,SAAS,gBAAgB,QAAiB,KAAsB;AACrE,QAAM,OAAO,SAASA,QAAO,OAAO,QAAQ,IAAI;AAChD,aAAO,wBAAK,MAAM,WAAW,QAAQ;AACvC;AAEA,SAAS,kBAAkB,OAAoB,QAAiB,KAAsB;AACpF,QAAM,OAAO,SAASA,QAAO,OAAO,QAAQ,IAAI;AAChD,SAAO,SAAS,MAAM,sBAAkB,wBAAK,MAAM,MAAM,gBAAgB;AAC3E;AAMA,eAAe,sBAAsB,GAA4B;AAC/D,QAAM,QAAI,2BAAQ,CAAC;AACnB,QAAM,QAAI,4BAAS,CAAC;AACpB,MAAI;AACF,eAAO,wBAAK,UAAM,2BAAS,CAAC,GAAG,CAAC;AAAA,EAClC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAe,cAAc,QAAgB,UAAoC;AAC/E,MAAI;AACF,UAAM,qBAAiB,2BAAQ,MAAM;AACrC,UAAM,uBAAmB,2BAAQ,QAAQ;AAGzC,QAAI;AACF,YAAM,CAAC,SAAS,OAAO,IAAI,MAAM,QAAQ,IAAI;AAAA,YAC3C,2BAAS,cAAc;AAAA,YACvB,2BAAS,gBAAgB;AAAA,MAC3B,CAAC;AACD,UAAI,YAAY,QAAS,QAAO;AAAA,IAClC,QAAQ;AAAA,IAER;AAGA,UAAM,oBAAoB,MAAM,sBAAsB,MAAM;AAC5D,UAAM,oBAAoB,MAAM,sBAAsB,QAAQ;AAC9D,QAAI,sBAAsB,kBAAmB,QAAO;AAGpD,QAAI;AACF,YAAM,QAAQ,UAAM,wBAAM,QAAQ;AAClC,UAAI,MAAM,eAAe,GAAG;AAC1B,cAAM,WAAW,UAAM,2BAAS,QAAQ;AACxC,gBAAI,+BAAQ,2BAAQ,QAAQ,GAAG,QAAQ,MAAM,eAAgB,QAAO;AACpE,kBAAM,qBAAG,QAAQ;AAAA,MACnB,OAAO;AACL,kBAAM,qBAAG,UAAU,EAAE,WAAW,KAAK,CAAC;AAAA,MACxC;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,UAAM,cAAU,2BAAQ,QAAQ;AAChC,cAAM,wBAAM,SAAS,EAAE,WAAW,KAAK,CAAC;AAExC,UAAM,cAAc,MAAM,sBAAsB,OAAO;AACvD,UAAM,cAAU,4BAAS,aAAa,MAAM;AAE5C,cAAM,0BAAQ,SAAS,QAAQ;AAC/B,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,eAAe,QAAQ,KAAa,MAA6B;AAC/D,YAAM,wBAAM,MAAM,EAAE,WAAW,KAAK,CAAC;AAGrC,YAAM,qBAAG,KAAK,MAAM;AAAA,IAClB,WAAW;AAAA,IACX,aAAa;AAAA,IACb,QAAQ,CAAC,YAAY;AACnB,YAAM,WAAO,4BAAS,OAAO;AAC7B,aAAO,SAAS,UAAU,SAAS;AAAA,IACrC;AAAA,EACF,CAAC;AACH;AAKA,eAAsB,aACpB,OACA,OACA,UAII,CAAC,GACmB;AACxB,QAAM,WAAW,QAAQ,UAAU;AACnC,QAAM,MAAM,QAAQ,OAAO,QAAQ,IAAI;AACvC,QAAM,OAAO,QAAQ,QAAQ;AAE7B,QAAM,YAAY,MAAM;AACxB,QAAM,mBAAe,wBAAK,gBAAgB,UAAU,GAAG,GAAG,SAAS;AACnE,QAAM,eAAW,wBAAK,kBAAkB,OAAO,UAAU,GAAG,GAAG,SAAS;AAExE,MAAI;AACF,QAAI,SAAS,QAAQ;AAEnB,gBAAM,qBAAG,UAAU,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACnD,YAAM,QAAQ,MAAM,SAAS,QAAQ;AACrC,aAAO,EAAE,OAAO,WAAW,OAAO,MAAM,IAAI,SAAS,MAAM,MAAM,UAAU,MAAM,OAAO;AAAA,IAC1F;AAGA,cAAM,qBAAG,cAAc,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACvD,UAAM,QAAQ,MAAM,SAAS,YAAY;AAGzC,QAAI,MAAM,qBAAqB,oBAAoB,CAAC,UAAU;AAE5D,aAAO;AAAA,QACL,OAAO;AAAA,QACP,OAAO,MAAM;AAAA,QACb,SAAS;AAAA,QACT,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,IACF;AAEA,UAAM,KAAK,MAAM,cAAc,cAAc,QAAQ;AACrD,QAAI,CAAC,IAAI;AAEP,gBAAM,qBAAG,UAAU,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACnD,YAAM,QAAQ,MAAM,SAAS,QAAQ;AAAA,IACvC;AAEA,WAAO;AAAA,MACL,OAAO;AAAA,MACP,OAAO,MAAM;AAAA,MACb,SAAS;AAAA,MACT,MAAM,KAAK,WAAW;AAAA,MACtB,MAAM;AAAA,IACR;AAAA,EACF,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,OAAO;AAAA,MACP,OAAO,MAAM;AAAA,MACb,SAAS;AAAA,MACT,MAAM;AAAA,MACN;AAAA,MACA,OAAO,eAAe,QAAQ,IAAI,UAAU;AAAA,IAC9C;AAAA,EACF;AACF;AAKA,eAAsB,cACpB,QACA,QACA,UAII,CAAC,GACqB;AAC1B,QAAM,UAA2B,CAAC;AAClC,aAAW,SAAS,QAAQ;AAC1B,eAAW,SAAS,QAAQ;AAC1B,cAAQ,KAAK,MAAM,aAAa,OAAO,OAAO,OAAO,CAAC;AAAA,IACxD;AAAA,EACF;AACA,SAAO;AACT;;;AH5MA,SAAS,QAAQ,SAAiB;AAChC,QAAM,SAAS,CAAC,UAAK,UAAK,UAAK,UAAK,UAAK,UAAK,UAAK,UAAK,UAAK,QAAG;AAChE,MAAI,IAAI;AACR,MAAI,UAAU;AACd,QAAM,QAAQ,YAAY,MAAM;AAC9B,YAAQ,OAAO,MAAM,KAAK,OAAO,MAAM,OAAO,MAAM,CAAC,IAAI,OAAO,EAAE;AAAA,EACpE,GAAG,EAAE;AACL,SAAO;AAAA,IACL,QAAQ,MAAc;AACpB,oBAAc,KAAK;AACnB,cAAQ,OAAO,MAAM,KAAK,kBAAAC,QAAG,MAAM,QAAG,CAAC,IAAI,IAAI;AAAA,CAAI;AACnD,gBAAU;AAAA,IACZ;AAAA,IACA,KAAK,MAAc;AACjB,oBAAc,KAAK;AACnB,cAAQ,OAAO,MAAM,KAAK,kBAAAA,QAAG,IAAI,QAAG,CAAC,IAAI,IAAI;AAAA,CAAI;AACjD,gBAAU;AAAA,IACZ;AAAA,IACA,OAAO;AACL,oBAAc,KAAK;AACnB,cAAQ,OAAO,MAAM,OAAO,IAAI,OAAO,QAAQ,SAAS,CAAC,IAAI,IAAI;AACjE,gBAAU;AAAA,IACZ;AAAA,EACF;AACF;AAIA,IAAM,QAAQ;AAAA,EACZ,OAAO;AAAA,IACL,WAAW,CAAC,SAAiB,kBAAAA,QAAG,KAAK,IAAI;AAAA,EAC3C;AACF;AAIA,eAAe,aAAa,QAA2C;AACrE,MAAI,OAAO,WAAW,GAAG;AACvB,YAAQ,IAAI,kBAAAA,QAAG,OAAO,uCAAuC,CAAC;AAC9D,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,UAAU,OAAO,IAAI,CAAC,OAAO;AAAA,IACjC,MAAM,GAAG,kBAAAA,QAAG,KAAK,EAAE,IAAI,CAAC,IAAI,kBAAAA,QAAG,IAAI,UAAK,EAAE,WAAW,EAAE,CAAC;AAAA,IACxD,OAAO;AAAA,EACT,EAAE;AAGF,UAAQ,QAAQ;AAAA,IACd,MAAM,kBAAAA,QAAG,OAAO,mBAAmB;AAAA,IACnC,OAAO;AAAA,EACT,CAAC;AAED,QAAM,WAAW,UAAM,yBAAS;AAAA,IAC9B,SAAS;AAAA,IACT;AAAA,IACA,UAAU;AAAA,IACV,MAAM;AAAA,IACN,OAAO;AAAA,MACL,GAAG;AAAA,MACH,UAAU;AAAA,IACZ;AAAA,EACF,CAAC;AAGD,MAAI,SAAS,KAAK,CAAC,MAA2B,MAA4B,SAAS,GAAG;AACpF,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,eAAe,eAAuC;AACpD,QAAM,WAAW,sBAAsB;AACvC,QAAM,cAAc,IAAI,IAAI,SAAS,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAErD,QAAM,UAAU,OAAO,IAAI,CAAC,OAAO;AAAA,IACjC,MAAM,YAAY,IAAI,EAAE,EAAE,IACtB,GAAG,EAAE,WAAW,IAAI,kBAAAA,QAAG,MAAM,YAAY,CAAC,KAC1C,EAAE;AAAA,IACN,OAAO;AAAA,IACP,SAAS,YAAY,IAAI,EAAE,EAAE;AAAA,EAC/B,EAAE;AAEF,aAAO,yBAAS;AAAA,IACd,SAAS;AAAA,IACT;AAAA,IACA,UAAU;AAAA,IACV,MAAM;AAAA,IACN,OAAO;AAAA,MACL,GAAG;AAAA,MACH,UAAU;AAAA,IACZ;AAAA,IACA,UAAU,CAAC,WAAW;AACpB,UAAI,OAAO,WAAW,EAAG,QAAO;AAChC,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AACH;AAEA,eAAe,cAAgC;AAC7C,aAAO,uBAAO;AAAA,IACZ,SAAS;AAAA,IACT,SAAS;AAAA,MACP;AAAA,QACE,MAAM,GAAG,kBAAAA,QAAG,KAAK,SAAS,CAAC;AAAA,QAC3B,OAAO;AAAA,MACT;AAAA,MACA;AAAA,QACE,MAAM,GAAG,kBAAAA,QAAG,KAAK,QAAQ,CAAC;AAAA,QAC1B,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,SAAS;AAAA,EACX,CAAC;AACH;AAEA,eAAe,aAAmC;AAChD,aAAO,uBAAO;AAAA,IACZ,SAAS;AAAA,IACT,SAAS;AAAA,MACP;AAAA,QACE,MAAM,GAAG,kBAAAA,QAAG,KAAK,SAAS,CAAC,IAAI,kBAAAA,QAAG,MAAM,eAAe,CAAC;AAAA,QACxD,OAAO;AAAA,MACT;AAAA,MACA;AAAA,QACE,MAAM,GAAG,kBAAAA,QAAG,KAAK,MAAM,CAAC;AAAA,QACxB,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,SAAS;AAAA,EACX,CAAC;AACH;AAEA,eAAe,oBACb,QACA,QACA,QACA,MACkB;AAClB,UAAQ,IAAI,OAAO,kBAAAA,QAAG,KAAK,4DAA8B,CAAC;AAC1D,UAAQ,IAAI,kBAAAA,QAAG,MAAM,cAAc,kBAAAA,QAAG,KAAK,OAAO,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC,CAAC,EAAE,CAAC;AACnF,UAAQ,IAAI,kBAAAA,QAAG,MAAM,cAAc,kBAAAA,QAAG,KAAK,OAAO,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC,CAAC,EAAE,CAAC;AAC1F,UAAQ,IAAI,kBAAAA,QAAG,MAAM,cAAc,kBAAAA,QAAG,KAAK,SAAS,WAAW,SAAS,CAAC,EAAE,CAAC;AAC5E,UAAQ,IAAI,kBAAAA,QAAG,MAAM,cAAc,kBAAAA,QAAG,KAAK,SAAS,YAAY,2CAA2C,MAAM,CAAC,EAAE,CAAC;AACrH,UAAQ,IAAI,EAAE;AAEd,aAAO,wBAAQ;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,EACX,CAAC;AACH;AAEA,SAAS,aAAa,SAAgC;AACpD,QAAM,YAAY,QAAQ,OAAO,CAAC,MAAM,EAAE,OAAO;AACjD,QAAM,SAAS,QAAQ,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO;AAE/C,UAAQ,IAAI,EAAE;AACd,MAAI,UAAU,SAAS,GAAG;AACxB,YAAQ,IAAI,kBAAAA,QAAG,MAAM,UAAK,UAAU,MAAM,mCAAmC,CAAC;AAC9E,eAAW,KAAK,WAAW;AACzB,cAAQ,IAAI,kBAAAA,QAAG,IAAI,KAAK,EAAE,KAAK,WAAM,EAAE,KAAK,KAAK,EAAE,IAAI,YAAO,EAAE,IAAI,EAAE,CAAC;AAAA,IACzE;AAAA,EACF;AACA,MAAI,OAAO,SAAS,GAAG;AACrB,YAAQ,IAAI,kBAAAA,QAAG,IAAI,UAAK,OAAO,MAAM,mBAAmB,CAAC;AACzD,eAAW,KAAK,QAAQ;AACtB,cAAQ,IAAI,kBAAAA,QAAG,IAAI,KAAK,EAAE,KAAK,WAAM,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,CAAC;AAAA,IAC7D;AAAA,EACF;AAGA,MAAI,UAAU,SAAS,GAAG;AACxB,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,kBAAAA,QAAG,KAAK,MAAM,CAAC;AAC3B,YAAQ,IAAI,kBAAAA,QAAG,IAAI,wEAAwE,CAAC;AAC5F,YAAQ,IAAI,kBAAAA,QAAG,IAAI,2DAA2D,CAAC;AAAA,EACjF;AACF;AAIA,eAAsB,SAAwB;AAC5C,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,kBAAAA,QAAG,KAAK,kBAAAA,QAAG,KAAK,mBAAmB,CAAC,CAAC;AACjD,UAAQ,IAAI,kBAAAA,QAAG,IAAI,gCAAgC,CAAC;AACpD,UAAQ,IAAI,EAAE;AAGd,QAAM,kBAAkB,QAAQ,uBAAuB;AACvD,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,eAAe;AAC9B,oBAAgB,QAAQ,SAAS,OAAO,MAAM,YAAY;AAAA,EAC5D,SAAS,KAAK;AACZ,oBAAgB,KAAK,4BAA4B;AACjD,YAAQ,MAAM,GAAG;AACjB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,OAAO,WAAW,GAAG;AACvB,YAAQ,IAAI,kBAAAA,QAAG,OAAO,kEAAkE,CAAC;AACzF,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,QAAM,iBAAiB,MAAM,aAAa,MAAM;AAChD,MAAI,eAAe,WAAW,GAAG;AAC/B,YAAQ,IAAI,kBAAAA,QAAG,OAAO,8BAA8B,CAAC;AACrD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,QAAM,iBAAiB,MAAM,aAAa;AAG1C,QAAM,SAAS,MAAM,YAAY;AAGjC,QAAM,OAAO,MAAM,WAAW;AAG9B,QAAM,YAAY,MAAM,oBAAoB,gBAAgB,gBAAgB,QAAQ,IAAI;AACxF,MAAI,CAAC,WAAW;AACd,YAAQ,IAAI,kBAAAA,QAAG,OAAO,yBAAyB,CAAC;AAChD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,QAAM,iBAAiB,QAAQ,sBAAsB;AACrD,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,cAAc,gBAAgB,gBAAgB,EAAE,QAAQ,KAAK,CAAC;AAC9E,mBAAe,KAAK;AAAA,EACtB,SAAS,KAAK;AACZ,mBAAe,KAAK,sBAAsB;AAC1C,YAAQ,MAAM,GAAG;AACjB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,eAAa,OAAO;AAEpB,MAAI,QAAQ,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG;AACnC,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;;;AI7PA,IAAAC,qBAAe;AAKf,SAAS,WAAiB;AACxB,UAAQ,IAAI;AAAA,EACZ,mBAAAC,QAAG,KAAK,mBAAAA,QAAG,KAAK,mBAAmB,CAAC,CAAC;AAAA;AAAA,EAErC,mBAAAA,QAAG,KAAK,QAAQ,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAKjB,mBAAAA,QAAG,KAAK,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWnB,mBAAAA,QAAG,KAAK,WAAW,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMpB,mBAAAA,QAAG,KAAK,SAAS,CAAC;AAAA,IAChB,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,EAAE,KAAK,IAAI,CAAC;AAAA;AAAA,EAE5D,mBAAAA,QAAG,IAAI,6CAA6C,CAAC;AAAA,CACtD;AACD;AAaA,SAAS,UAAUC,OAA4B;AAC7C,QAAM,SAAqB;AAAA,IACzB,MAAM;AAAA,IACN,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,QAAQ,CAAC;AAAA,IACT,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,KAAK;AAAA,EACP;AAEA,MAAI,IAAI;AACR,SAAO,IAAIA,MAAK,QAAQ;AACtB,UAAM,MAAMA,MAAK,CAAC;AAClB,YAAQ,KAAK;AAAA,MACX,KAAK;AAAA,MACL,KAAK;AACH,eAAO,OAAO;AACd;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH,eAAO,OAAO;AACd;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH;AACA,eAAO,IAAIA,MAAK,UAAU,CAACA,MAAK,CAAC,EAAE,WAAW,GAAG,GAAG;AAClD,iBAAO,QAAQ,KAAKA,MAAK,CAAC,CAAC;AAC3B;AAAA,QACF;AACA;AACA;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH;AACA,eAAO,IAAIA,MAAK,UAAU,CAACA,MAAK,CAAC,EAAE,WAAW,GAAG,GAAG;AAClD,iBAAO,OAAO,KAAKA,MAAK,CAAC,CAAC;AAC1B;AAAA,QACF;AACA;AACA;AAAA,MACF,KAAK;AACH,eAAO,YAAY;AACnB;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH,eAAO,SAAS;AAChB;AAAA,MACF,KAAK;AACH,eAAO,OAAO;AACd;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH,eAAO,MAAM;AACb;AAAA,MACF;AAEE,YAAI,CAAC,IAAI,WAAW,GAAG,GAAG;AACxB,iBAAO,QAAQ,KAAK,GAAG;AAAA,QACzB;AAAA,IACJ;AACA;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAsB,OAAO,SAAkC;AAC7D,QAAMA,QAAO,UAAU,OAAO;AAE9B,MAAIA,MAAK,MAAM;AACb,aAAS;AACT,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAIA,MAAK,MAAM;AACb,UAAM,SAAS,MAAM,eAAe;AACpC,QAAI,OAAO,WAAW,GAAG;AACvB,cAAQ,IAAI,mBAAAD,QAAG,OAAO,kBAAkB,CAAC;AAAA,IAC3C,OAAO;AACL,cAAQ,IAAI,mBAAAA,QAAG,KAAK,qBAAqB,OAAO,MAAM;AAAA,CAAM,CAAC;AAC7D,iBAAW,KAAK,QAAQ;AACtB,gBAAQ,IAAI,KAAK,mBAAAA,QAAG,KAAK,mBAAAA,QAAG,KAAK,EAAE,IAAI,CAAC,CAAC,EAAE;AAC3C,gBAAQ,IAAI,OAAO,mBAAAA,QAAG,IAAI,EAAE,WAAW,CAAC,EAAE;AAC1C,gBAAQ,IAAI,iBAAiB,EAAE,QAAQ,EAAE;AACzC,gBAAQ,IAAI,EAAE;AAAA,MAChB;AAAA,IACF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,MAAIC,MAAK,QAAQ,SAAS,GAAG;AAC3B,UAAM,YAAY,MAAM,eAAe;AACvC,QAAI,kBAAkB;AAGtB,QAAI,CAACA,MAAK,QAAQ,SAAS,GAAG,GAAG;AAC/B,wBAAkB,UAAU;AAAA,QAAO,CAAC,MAClCA,MAAK,QAAQ,KAAK,CAAC,SAAS,EAAE,SAAS,IAAI;AAAA,MAC7C;AACA,YAAM,WAAWA,MAAK,QAAQ;AAAA,QAC5B,CAAC,SAAS,CAAC,UAAU,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAAA,MAClD;AACA,UAAI,SAAS,SAAS,GAAG;AACvB,gBAAQ,IAAI,mBAAAD,QAAG,OAAO,8BAA8B,SAAS,KAAK,IAAI,CAAC,EAAE,CAAC;AAAA,MAC5E;AAAA,IACF;AAEA,QAAI,gBAAgB,WAAW,GAAG;AAChC,cAAQ,IAAI,mBAAAA,QAAG,IAAI,sCAAsC,CAAC;AAC1D,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,QAAI,cAAcC,MAAK,OACpB,IAAI,CAAC,OAAO,SAAS,EAAE,CAAC,EACxB,OAAO,CAAC,MAAkC,CAAC,CAAC,CAAC;AAEhD,QAAIA,MAAK,WAAW;AAElB,oBAAc;AAAA,IAChB;AAEA,QAAI,YAAY,WAAW,GAAG;AAC5B,cAAQ,IAAI,mBAAAD,QAAG,IAAI,wDAAwD,CAAC;AAC5E,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,UAAM,OAAOC,MAAK,OAAO,SAAS;AAElC,YAAQ,IAAI,mBAAAD,QAAG,KAAK,oDAAsB,CAAC;AAC3C,YAAQ,IAAI,aAAa,mBAAAA,QAAG,KAAK,gBAAgB,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC,CAAC,EAAE;AACjF,YAAQ,IAAI,aAAa,mBAAAA,QAAG,KAAK,YAAY,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC,CAAC,EAAE;AACpF,YAAQ,IAAI,aAAa,mBAAAA,QAAG,KAAKC,MAAK,SAAS,WAAW,SAAS,CAAC,EAAE;AACtE,YAAQ,IAAI,aAAa,mBAAAD,QAAG,KAAK,IAAI,CAAC,EAAE;AACxC,YAAQ,IAAI,EAAE;AAEd,UAAM,UAAU,MAAM,cAAc,iBAAiB,aAAa;AAAA,MAChE,QAAQC,MAAK;AAAA,MACb;AAAA,IACF,CAAC;AAED,UAAM,YAAY,QAAQ,OAAO,CAAC,MAAM,EAAE,OAAO;AACjD,UAAM,SAAS,QAAQ,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO;AAE/C,QAAI,UAAU,SAAS,GAAG;AACxB,cAAQ,IAAI,mBAAAD,QAAG,MAAM,UAAK,UAAU,MAAM,yBAAyB,CAAC;AAAA,IACtE;AACA,QAAI,OAAO,SAAS,GAAG;AACrB,cAAQ,IAAI,mBAAAA,QAAG,IAAI,UAAK,OAAO,MAAM,SAAS,CAAC;AAC/C,iBAAW,KAAK,QAAQ;AACtB,gBAAQ,IAAI,mBAAAA,QAAG,IAAI,KAAK,EAAE,KAAK,WAAM,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,CAAC;AAAA,MAC7D;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,WAAS;AACX;;;AC7MA,IAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AAEjC,IAAI,KAAK,SAAS,GAAG;AACnB,SAAO,IAAI,EAAE,MAAM,CAAC,QAAQ;AAC1B,YAAQ,MAAM,UAAU,IAAI,OAAO;AACnC,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH,OAAO;AACL,SAAO,EAAE,MAAM,CAAC,QAAQ;AACtB,YAAQ,MAAM,qBAAqB,GAAG;AACtC,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;","names":["import_node_fs","import_node_path","import_promises","import_node_path","import_node_os","home","pc","import_picocolors","pc","args"]}
|
|
1
|
+
{"version":3,"sources":["../src/tui.ts","../src/agents.ts","../src/skills.ts","../src/installer.ts","../src/cli-args.ts","../src/cli.ts"],"sourcesContent":["import { search, checkbox, select, confirm } from '@inquirer/prompts';\nimport pc from 'picocolors';\nimport { AGENTS, detectInstalledAgents, type AgentConfig } from './agents.js';\nimport { discoverSkills, type SkillMeta } from './skills.js';\nimport { installSkills, type InstallMode, type InstallResult } from './installer.js';\n\n// ─── simple spinner ────────────────────────────────────────────\n\nfunction spinner(message: string) {\n const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n let i = 0;\n let running = true;\n const timer = setInterval(() => {\n process.stderr.write(`\\r${frames[i++ % frames.length]} ${message}`);\n }, 80);\n return {\n succeed(text: string) {\n clearInterval(timer);\n process.stderr.write(`\\r${pc.green('✓')} ${text}\\n`);\n running = false;\n },\n fail(text: string) {\n clearInterval(timer);\n process.stderr.write(`\\r${pc.red('✗')} ${text}\\n`);\n running = false;\n },\n stop() {\n clearInterval(timer);\n process.stderr.write('\\r' + ' '.repeat(message.length + 4) + '\\r');\n running = false;\n },\n };\n}\n\n// ─── theme helpers ──────────────────────────────────────────────\n\nconst theme = {\n style: {\n highlight: (text: string) => pc.cyan(text),\n },\n};\n\n// ─── steps ──────────────────────────────────────────────────────\n\nasync function selectSkills(skills: SkillMeta[]): Promise<SkillMeta[]> {\n if (skills.length === 0) {\n console.log(pc.yellow('No skills found in skills/ directory.'));\n return [];\n }\n\n const choices = skills.map((s) => ({\n name: `${pc.bold(s.name)} ${pc.dim(`— ${s.description}`)}`,\n value: s,\n }));\n\n // Add a \"select all\" option\n choices.unshift({\n name: pc.italic('Select all skills'),\n value: '__all__' as unknown as SkillMeta,\n });\n\n const selected = await checkbox({\n message: 'Select skills to install:',\n choices,\n pageSize: 15,\n loop: false,\n theme: {\n ...theme,\n helpMode: 'auto',\n },\n });\n\n // Handle \"select all\"\n if (selected.some((s: SkillMeta | string) => (s as unknown as string) === '__all__')) {\n return skills;\n }\n\n return selected as SkillMeta[];\n}\n\nasync function selectAgents(): Promise<AgentConfig[]> {\n const detected = detectInstalledAgents();\n const detectedIds = new Set(detected.map((a) => a.id));\n\n const choices = AGENTS.map((a) => ({\n name: detectedIds.has(a.id)\n ? `${a.displayName} ${pc.green('(detected)')}`\n : a.displayName,\n value: a,\n checked: detectedIds.has(a.id),\n }));\n\n return checkbox({\n message: 'Select target agents:',\n choices,\n pageSize: 12,\n loop: false,\n theme: {\n ...theme,\n helpMode: 'auto',\n },\n validate: (answer) => {\n if (answer.length === 0) return 'Select at least one agent.';\n return true;\n },\n });\n}\n\nasync function selectScope(): Promise<boolean> {\n return select({\n message: 'Install scope:',\n choices: [\n {\n name: `${pc.bold('Project')} — install to ./<agent>/skills/ (shared with your team)`,\n value: false,\n },\n {\n name: `${pc.bold('Global')} — install to ~/<agent>/skills/ (available everywhere)`,\n value: true,\n },\n ],\n default: false,\n });\n}\n\nasync function selectMode(): Promise<InstallMode> {\n return select({\n message: 'Install method:',\n choices: [\n {\n name: `${pc.bold('Symlink')} ${pc.green('(recommended)')} — single source of truth at .agents/skills/, symlinked to agent dirs. Easy updates.`,\n value: 'symlink',\n },\n {\n name: `${pc.bold('Copy')} — independent copies for each agent. Use when symlinks aren't supported.`,\n value: 'copy',\n },\n ],\n default: 'symlink',\n });\n}\n\nasync function confirmInstallation(\n skills: SkillMeta[],\n agents: AgentConfig[],\n global: boolean,\n mode: InstallMode,\n): Promise<boolean> {\n console.log('\\n' + pc.cyan('─── Installation summary ───'));\n console.log(pc.white(` Skills: ${pc.bold(skills.map((s) => s.name).join(', '))}`));\n console.log(pc.white(` Agents: ${pc.bold(agents.map((a) => a.displayName).join(', '))}`));\n console.log(pc.white(` Scope: ${pc.bold(global ? 'Global' : 'Project')}`));\n console.log(pc.white(` Method: ${pc.bold(mode === 'symlink' ? 'Symlink (canonical at .agents/skills/)' : 'Copy')}`));\n console.log('');\n\n return confirm({\n message: 'Proceed with installation?',\n default: true,\n });\n}\n\nfunction printResults(results: InstallResult[]): void {\n const succeeded = results.filter((r) => r.success);\n const failed = results.filter((r) => !r.success);\n\n console.log('');\n if (succeeded.length > 0) {\n console.log(pc.green(`✓ ${succeeded.length} skill(s) installed successfully:`));\n for (const r of succeeded) {\n console.log(pc.dim(` ${r.skill} → ${r.agent} (${r.mode}) → ${r.path}`));\n }\n }\n if (failed.length > 0) {\n console.log(pc.red(`✗ ${failed.length} skill(s) failed:`));\n for (const r of failed) {\n console.log(pc.red(` ${r.skill} → ${r.agent}: ${r.error}`));\n }\n }\n\n // Post-install hints\n if (succeeded.length > 0) {\n console.log('');\n console.log(pc.cyan('Tip:'));\n console.log(pc.dim(' Skills are installed. Restart your agent for changes to take effect.'));\n console.log(pc.dim(' To update, re-run this installer or git pull + re-link.'));\n }\n}\n\n// ─── main ───────────────────────────────────────────────────────\n\nexport async function runTui(): Promise<void> {\n console.log('');\n console.log(pc.bold(pc.cyan('@katipwork/skills')));\n console.log(pc.dim('Curated agent skills installer'));\n console.log('');\n\n // 1. Discover skills\n const discoverSpinner = spinner('Discovering skills...');\n let skills: SkillMeta[];\n try {\n skills = await discoverSkills();\n discoverSpinner.succeed(`Found ${skills.length} skill(s).`);\n } catch (err) {\n discoverSpinner.fail('Failed to discover skills.');\n console.error(err);\n process.exit(1);\n }\n\n if (skills.length === 0) {\n console.log(pc.yellow('No skills found. Add SKILL.md files under skills/ and try again.'));\n process.exit(0);\n }\n\n // 2. Select skills\n const selectedSkills = await selectSkills(skills);\n if (selectedSkills.length === 0) {\n console.log(pc.yellow('No skills selected. Exiting.'));\n process.exit(0);\n }\n\n // 3. Select agents\n const selectedAgents = await selectAgents();\n\n // 4. Scope\n const global = await selectScope();\n\n // 5. Mode\n const mode = await selectMode();\n\n // 6. Confirm\n const confirmed = await confirmInstallation(selectedSkills, selectedAgents, global, mode);\n if (!confirmed) {\n console.log(pc.yellow('Installation cancelled.'));\n process.exit(0);\n }\n\n // 7. Install\n const installSpinner = spinner('Installing skills...');\n let results: InstallResult[];\n try {\n results = await installSkills(selectedSkills, selectedAgents, { global, mode });\n installSpinner.stop();\n } catch (err) {\n installSpinner.fail('Installation failed.');\n console.error(err);\n process.exit(1);\n }\n\n printResults(results);\n\n if (results.some((r) => !r.success)) {\n process.exit(1);\n }\n}\n","import { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { existsSync } from 'node:fs';\n\nconst home = homedir();\n\nexport interface AgentConfig {\n /** CLI flag value, e.g. \"claude-code\" */\n id: string;\n /** Human-readable label */\n displayName: string;\n /** Relative path from project root for project-scoped installs */\n projectSkillsDir: string;\n /** Absolute path for global installs */\n globalSkillsDir: string;\n /** Quick check if agent is installed */\n detectInstalled: () => boolean;\n}\n\nexport type AgentId = (typeof AGENTS)[number]['id'];\n\n/**\n * All supported coding agents.\n * Each has a project-skills dir (relative to CWD) and a global-skills dir (absolute).\n */\nexport const AGENTS: AgentConfig[] = [\n {\n id: 'pi',\n displayName: 'Pi',\n projectSkillsDir: '.pi/skills',\n globalSkillsDir: join(home, '.pi/agent/skills'),\n detectInstalled: () => existsSync(join(home, '.pi/agent')),\n },\n {\n id: 'claude-code',\n displayName: 'Claude Code',\n projectSkillsDir: '.claude/skills',\n globalSkillsDir: join(home, '.claude/skills'),\n detectInstalled: () => existsSync(join(home, '.claude')),\n },\n {\n id: 'codex',\n displayName: 'Codex',\n projectSkillsDir: '.agents/skills',\n globalSkillsDir: join(home, '.codex/skills'),\n detectInstalled: () => existsSync(join(home, '.codex')),\n },\n {\n id: 'github-copilot',\n displayName: 'GitHub Copilot',\n projectSkillsDir: '.agents/skills',\n globalSkillsDir: join(home, '.copilot/skills'),\n detectInstalled: () => existsSync(join(home, '.copilot')),\n },\n {\n id: 'hermes-agent',\n displayName: 'Hermes Agent',\n projectSkillsDir: '.hermes/skills',\n globalSkillsDir: join(home, '.hermes/skills'),\n detectInstalled: () => existsSync(join(home, '.hermes')),\n },\n {\n id: 'cursor',\n displayName: 'Cursor',\n projectSkillsDir: '.agents/skills',\n globalSkillsDir: join(home, '.cursor/skills'),\n detectInstalled: () => existsSync(join(home, '.cursor')),\n },\n {\n id: 'opencode',\n displayName: 'OpenCode',\n projectSkillsDir: '.agents/skills',\n globalSkillsDir: join(home, '.config/opencode/skills'),\n detectInstalled: () => existsSync(join(home, '.config/opencode')),\n },\n];\n\n/** Lookup agent config by id */\nexport function getAgent(id: string): AgentConfig | undefined {\n return AGENTS.find((a) => a.id === id);\n}\n\n/** Detect which agents are installed on this machine */\nexport function detectInstalledAgents(): AgentConfig[] {\n return AGENTS.filter((a) => a.detectInstalled());\n}\n","import { readFileSync, existsSync } from 'node:fs';\nimport { readdir } from 'node:fs/promises';\nimport { join, basename, dirname } from 'node:path';\n\n// In CJS, __dirname is a global pointing to the directory of the current file.\n// After build, __dirname is dist/ and skills/ is at ../skills.\ndeclare const __dirname: string;\n\nexport interface SkillMeta {\n /** Skill name from SKILL.md frontmatter */\n name: string;\n /** Description from SKILL.md frontmatter */\n description: string;\n /** Path to the skill directory on disk */\n dirPath: string;\n /** Which category/bucket the skill lives in, e.g. \"engineering\" */\n category: string;\n}\n\n/**\n * Parse YAML frontmatter from a SKILL.md file.\n * Minimal parser that handles `---` delimited frontmatter.\n */\nfunction parseFrontmatter(content: string): { data: Record<string, string> } | null {\n const match = content.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---/);\n if (!match) return null;\n\n const data: Record<string, string> = {};\n for (const line of match[1].split('\\n')) {\n const colonIdx = line.indexOf(':');\n if (colonIdx === -1) continue;\n const key = line.slice(0, colonIdx).trim();\n const value = line.slice(colonIdx + 1).trim();\n if (key && value) data[key] = value;\n }\n return { data };\n}\n\n/**\n * Find the skills/ directory.\n * When built: dist/ → ../skills\n * When running from source (tsx): src/ → ../skills\n * When installed globally: <package>/dist/ → ../skills\n */\nfunction getSkillsRoot(): string {\n // dist/ → ../skills (bundled case)\n const bundledPath = join(__dirname, '..', 'skills');\n if (existsSync(bundledPath)) return bundledPath;\n\n // CWD fallback\n return join(process.cwd(), 'skills');\n}\n\n/**\n * Discover all skills under skills/.\n * Skills can be at skills/<name>/SKILL.md or skills/<category>/<name>/SKILL.md.\n */\nexport async function discoverSkills(): Promise<SkillMeta[]> {\n const skillsRoot = getSkillsRoot();\n const skills: SkillMeta[] = [];\n\n if (!existsSync(skillsRoot)) {\n return skills;\n }\n\n async function walk(dir: string, category: string = ''): Promise<void> {\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;\n\n const fullPath = join(dir, entry.name);\n const skillMdPath = join(fullPath, 'SKILL.md');\n\n if (existsSync(skillMdPath)) {\n // This is a skill directory (has SKILL.md)\n try {\n const content = readFileSync(skillMdPath, 'utf-8');\n const parsed = parseFrontmatter(content);\n if (parsed && parsed.data.name) {\n skills.push({\n name: parsed.data.name,\n description: parsed.data.description || '',\n dirPath: fullPath,\n category: category || (dir === skillsRoot ? 'general' : basename(dirname(fullPath))),\n });\n }\n } catch {\n // skip broken skills\n }\n } else {\n // Recurse into subdirectories (category level)\n const subCategory = category ? `${category}/${entry.name}` : entry.name;\n await walk(fullPath, category || entry.name);\n }\n }\n }\n\n await walk(skillsRoot);\n return skills.sort((a, b) => a.name.localeCompare(b.name));\n}\n","import { existsSync } from 'node:fs';\nimport {\n mkdir,\n cp,\n symlink,\n rm,\n readlink,\n lstat,\n realpath,\n stat,\n} from 'node:fs/promises';\nimport { join, relative, dirname, resolve, basename } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { AgentConfig } from './agents.js';\nimport type { SkillMeta } from './skills.js';\n\nconst home = homedir();\n\nexport type InstallMode = 'symlink' | 'copy';\n\nexport interface InstallResult {\n skill: string;\n agent: string;\n success: boolean;\n path: string;\n mode: InstallMode;\n error?: string;\n}\n\n/** Canonical skills directory: .agents/skills/ */\nexport function getCanonicalDir(global: boolean, cwd?: string): string {\n const base = global ? home : cwd || process.cwd();\n return join(base, '.agents', 'skills');\n}\n\nfunction getAgentSkillsDir(agent: AgentConfig, global: boolean, cwd?: string): string {\n const base = global ? home : cwd || process.cwd();\n return global ? agent.globalSkillsDir : join(base, agent.projectSkillsDir);\n}\n\n/**\n * Resolve a symlink's parent directory to its real path.\n * Handles the case where e.g. ~/.claude/skills is a symlink to ~/.agents/skills.\n */\nasync function resolveParentSymlinks(p: string): Promise<string> {\n const d = dirname(p);\n const b = basename(p);\n try {\n return join(await realpath(d), b);\n } catch {\n return p;\n }\n}\n\n/**\n * Create a symlink at `linkPath` pointing to `target`.\n * Returns true on success, false if fallback to copy is needed.\n */\nasync function createSymlink(target: string, linkPath: string): Promise<boolean> {\n try {\n const resolvedTarget = resolve(target);\n const resolvedLinkPath = resolve(linkPath);\n\n // If they resolve to the same physical path, skip\n try {\n const [realTrg, realLnk] = await Promise.all([\n realpath(resolvedTarget),\n realpath(resolvedLinkPath),\n ]);\n if (realTrg === realLnk) return true;\n } catch {\n // paths may not exist yet — continue\n }\n\n // Check parent symlinks\n const realTgtWithParent = await resolveParentSymlinks(target);\n const realLnkWithParent = await resolveParentSymlinks(linkPath);\n if (realTgtWithParent === realLnkWithParent) return true;\n\n // Remove existing link/dir at target\n try {\n const stats = await lstat(linkPath);\n if (stats.isSymbolicLink()) {\n const existing = await readlink(linkPath);\n if (resolve(dirname(linkPath), existing) === resolvedTarget) return true;\n await rm(linkPath);\n } else {\n await rm(linkPath, { recursive: true });\n }\n } catch {\n // Doesn't exist — fine\n }\n\n const linkDir = dirname(linkPath);\n await mkdir(linkDir, { recursive: true });\n\n const realLinkDir = await resolveParentSymlinks(linkDir);\n const relPath = relative(realLinkDir, target);\n\n await symlink(relPath, linkPath);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Recursively copy a directory, skipping .git and node_modules.\n */\nasync function copyDir(src: string, dest: string): Promise<void> {\n await mkdir(dest, { recursive: true });\n\n // Use cp with recursive\n await cp(src, dest, {\n recursive: true,\n dereference: true,\n filter: (srcPath) => {\n const name = basename(srcPath);\n return name !== '.git' && name !== 'node_modules';\n },\n });\n}\n\n/**\n * Install a single skill for a single agent.\n */\nexport async function installSkill(\n skill: SkillMeta,\n agent: AgentConfig,\n options: {\n global?: boolean;\n cwd?: string;\n mode?: InstallMode;\n } = {},\n): Promise<InstallResult> {\n const isGlobal = options.global ?? false;\n const cwd = options.cwd ?? process.cwd();\n const mode = options.mode ?? 'symlink';\n\n const skillName = skill.name;\n const canonicalDir = join(getCanonicalDir(isGlobal, cwd), skillName);\n const agentDir = join(getAgentSkillsDir(agent, isGlobal, cwd), skillName);\n\n try {\n if (mode === 'copy') {\n // Copy directly to agent directory\n await rm(agentDir, { recursive: true, force: true });\n await copyDir(skill.dirPath, agentDir);\n return { skill: skillName, agent: agent.id, success: true, path: agentDir, mode: 'copy' };\n }\n\n // Symlink mode: copy to canonical, then symlink from agent dir\n await rm(canonicalDir, { recursive: true, force: true });\n await copyDir(skill.dirPath, canonicalDir);\n\n // For agents that share the universal .agents/skills dir, canonical is already correct\n if (agent.projectSkillsDir === '.agents/skills' && !isGlobal) {\n // Already installed at canonical location, nothing to symlink\n return {\n skill: skillName,\n agent: agent.id,\n success: true,\n path: canonicalDir,\n mode: 'symlink',\n };\n }\n\n const ok = await createSymlink(canonicalDir, agentDir);\n if (!ok) {\n // Fallback to copy\n await rm(agentDir, { recursive: true, force: true });\n await copyDir(skill.dirPath, agentDir);\n }\n\n return {\n skill: skillName,\n agent: agent.id,\n success: true,\n path: ok ? agentDir : agentDir,\n mode: 'symlink',\n };\n } catch (err) {\n return {\n skill: skillName,\n agent: agent.id,\n success: false,\n path: agentDir,\n mode,\n error: err instanceof Error ? err.message : 'Unknown error',\n };\n }\n}\n\n/**\n * Install multiple skills for multiple agents.\n */\nexport async function installSkills(\n skills: SkillMeta[],\n agents: AgentConfig[],\n options: {\n global?: boolean;\n cwd?: string;\n mode?: InstallMode;\n } = {},\n): Promise<InstallResult[]> {\n const results: InstallResult[] = [];\n for (const skill of skills) {\n for (const agent of agents) {\n results.push(await installSkill(skill, agent, options));\n }\n }\n return results;\n}\n","import pc from 'picocolors';\nimport { AGENTS, getAgent } from './agents.js';\nimport { discoverSkills } from './skills.js';\nimport { installSkills } from './installer.js';\n\nfunction showHelp(): void {\n console.log(`\n${pc.bold(pc.cyan('@katipwork/skills'))} — Curated agent skills installer\n\n${pc.bold('Usage:')}\n npx @katipwork/skills Interactive TUI (default)\n npx @katipwork/skills --list List available skills\n npx @katipwork/skills --install <skills...> [options]\n\n${pc.bold('Options:')}\n --help, -h Show this help\n --list, -l List available skills and exit\n --install, -i Install specific skills (followed by skill names)\n --agent, -a Target agent(s): pi, claude-code, codex, github-copilot,\n hermes-agent, cursor, opencode (can repeat)\n --all-agents Install to all detected agents\n --global, -g Install globally (~/<agent>/skills/)\n --copy Copy files instead of symlinking\n --yes, -y Skip confirmation prompt\n\n${pc.bold('Examples:')}\n npx @katipwork/skills\n npx @katipwork/skills --list\n npx @katipwork/skills -i code-review tdd -a pi -a claude-code -y\n npx @katipwork/skills -i '*' --all-agents --global\n\n${pc.bold('Agents:')}\n ${AGENTS.map((a) => `${a.id} (${a.displayName})`).join(', ')}\n\n${pc.dim('Source: https://github.com/katipwork/skills')}\n`);\n}\n\ninterface ParsedArgs {\n help: boolean;\n list: boolean;\n install: string[];\n agents: string[];\n allAgents: boolean;\n global: boolean;\n copy: boolean;\n yes: boolean;\n}\n\nfunction parseArgs(args: string[]): ParsedArgs {\n const result: ParsedArgs = {\n help: false,\n list: false,\n install: [],\n agents: [],\n allAgents: false,\n global: false,\n copy: false,\n yes: false,\n };\n\n let i = 0;\n while (i < args.length) {\n const arg = args[i];\n switch (arg) {\n case '--help':\n case '-h':\n result.help = true;\n break;\n case '--list':\n case '-l':\n result.list = true;\n break;\n case '--install':\n case '-i':\n i++;\n while (i < args.length && !args[i].startsWith('-')) {\n result.install.push(args[i]);\n i++;\n }\n i--; // compensate for loop increment\n break;\n case '--agent':\n case '-a':\n i++;\n while (i < args.length && !args[i].startsWith('-')) {\n result.agents.push(args[i]);\n i++;\n }\n i--;\n break;\n case '--all-agents':\n result.allAgents = true;\n break;\n case '--global':\n case '-g':\n result.global = true;\n break;\n case '--copy':\n result.copy = true;\n break;\n case '--yes':\n case '-y':\n result.yes = true;\n break;\n default:\n // Treat as skill name if not a flag\n if (!arg.startsWith('-')) {\n result.install.push(arg);\n }\n }\n i++;\n }\n\n return result;\n}\n\nexport async function runCli(rawArgs: string[]): Promise<void> {\n const args = parseArgs(rawArgs);\n\n if (args.help) {\n showHelp();\n process.exit(0);\n }\n\n if (args.list) {\n const skills = await discoverSkills();\n if (skills.length === 0) {\n console.log(pc.yellow('No skills found.'));\n } else {\n console.log(pc.bold(`Available skills (${skills.length}):\\n`));\n for (const s of skills) {\n console.log(` ${pc.bold(pc.cyan(s.name))}`);\n console.log(` ${pc.dim(s.description)}`);\n console.log(` category: ${s.category}`);\n console.log('');\n }\n }\n process.exit(0);\n }\n\n // Install mode\n if (args.install.length > 0) {\n const allSkills = await discoverSkills();\n let skillsToInstall = allSkills;\n\n // Filter by requested names unless '*' is specified\n if (!args.install.includes('*')) {\n skillsToInstall = allSkills.filter((s) =>\n args.install.some((name) => s.name === name),\n );\n const notFound = args.install.filter(\n (name) => !allSkills.some((s) => s.name === name),\n );\n if (notFound.length > 0) {\n console.log(pc.yellow(`Warning: skills not found: ${notFound.join(', ')}`));\n }\n }\n\n if (skillsToInstall.length === 0) {\n console.log(pc.red('No matching skills found to install.'));\n process.exit(1);\n }\n\n // Resolve agents\n let agentsToUse = args.agents\n .map((id) => getAgent(id))\n .filter((a): a is NonNullable<typeof a> => !!a);\n\n if (args.allAgents) {\n // Use all agents\n agentsToUse = AGENTS;\n }\n\n if (agentsToUse.length === 0) {\n console.log(pc.red('No agents specified. Use --agent <id> or --all-agents.'));\n process.exit(1);\n }\n\n const mode = args.copy ? 'copy' : 'symlink';\n\n console.log(pc.cyan('\\n─── Installing ───'));\n console.log(` Skills: ${pc.bold(skillsToInstall.map((s) => s.name).join(', '))}`);\n console.log(` Agents: ${pc.bold(agentsToUse.map((a) => a.displayName).join(', '))}`);\n console.log(` Scope: ${pc.bold(args.global ? 'Global' : 'Project')}`);\n console.log(` Method: ${pc.bold(mode)}`);\n console.log('');\n\n const results = await installSkills(skillsToInstall, agentsToUse, {\n global: args.global,\n mode,\n });\n\n const succeeded = results.filter((r) => r.success);\n const failed = results.filter((r) => !r.success);\n\n if (succeeded.length > 0) {\n console.log(pc.green(`✓ ${succeeded.length} installed successfully`));\n }\n if (failed.length > 0) {\n console.log(pc.red(`✗ ${failed.length} failed`));\n for (const r of failed) {\n console.log(pc.red(` ${r.skill} → ${r.agent}: ${r.error}`));\n }\n process.exit(1);\n }\n process.exit(0);\n }\n\n // No recognized mode — show help\n showHelp();\n}\n","// Shebang is added by tsup banner at build time.\nimport { runTui } from './tui.js';\nimport { runCli } from './cli-args.js';\n\n// If CLI arguments are provided (beyond just the binary), run in non-interactive mode.\n// Otherwise, launch the TUI.\nconst args = process.argv.slice(2);\n\nif (args.length > 0) {\n runCli(args).catch((err) => {\n console.error('Error:', err.message);\n process.exit(1);\n });\n} else {\n runTui().catch((err) => {\n console.error('Unexpected error:', err);\n process.exit(1);\n });\n}\n"],"mappings":"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","names":["import_node_fs","import_node_path","import_promises","import_node_path","import_node_os","home","pc","import_picocolors","pc","args"]}
|
package/package.json
CHANGED
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ddd-go-scaffold
|
|
3
|
+
description: Scaffold new DDD/CQRS/Event Sourcing domains in Go monorepos. Use when the user wants to create a new bounded context, scaffold a domain, add an event-sourced aggregate, or replicate the project's domain pattern. Covers commands, events (with upcasting), aggregates, projections, queries, policies, Kafka consumers, HTTP handlers, CLI wiring, and the deep-module principle.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# DDD Go Scaffold
|
|
7
|
+
|
|
8
|
+
Scaffolds a complete DDD/CQRS/Event Sourcing domain in a Go workspace monorepo. Every domain follows the same 13-file pattern — 4 files with business logic (domain-specific), 9 files that are purely mechanical (copy + sed).
|
|
9
|
+
|
|
10
|
+
Reference implementations in this project:
|
|
11
|
+
- `domains/ordering/` — canonical example with all patterns
|
|
12
|
+
- `domains/inventory/` — cross-domain consumer example
|
|
13
|
+
- `domains/billing/` — minimal domain example
|
|
14
|
+
- `SCAFFOLD.md` — condensed architecture reference
|
|
15
|
+
|
|
16
|
+
## Deep Module Invariant (NON-NEGOTIABLE)
|
|
17
|
+
|
|
18
|
+
Every package MUST hide complexity behind a small interface. The deletion test: remove the module and count the duplicated lines.
|
|
19
|
+
|
|
20
|
+
| Module | Interface methods | Lines hidden |
|
|
21
|
+
|--------|------------------|-------------|
|
|
22
|
+
| `eventstore` | Load, Append, AppendWithSnapshot (3) | ~200 |
|
|
23
|
+
| `outbox` | Append (1) | ~80 |
|
|
24
|
+
| `uow` | Execute (1) | ~120 |
|
|
25
|
+
| `cache` | Get, Set, Invalidate (3) | ~60 |
|
|
26
|
+
| `db` | Register (1) | ~300 |
|
|
27
|
+
| `cli` | New (1), Run (1) | ~200 |
|
|
28
|
+
| Aggregate | Handle (1) | All business rules |
|
|
29
|
+
|
|
30
|
+
NEVER put business logic outside the aggregate. Projections, policies, and HTTP handlers are stateless adapters.
|
|
31
|
+
|
|
32
|
+
## File Checklist (13 files per domain)
|
|
33
|
+
|
|
34
|
+
| # | File | Type | Customization |
|
|
35
|
+
|---|------|------|--------------|
|
|
36
|
+
| 1 | `commands.go` | DOMAIN | Command structs + marker interface |
|
|
37
|
+
| 2 | `events.go` | DOMAIN | Event structs + V1→V2 UnmarshalJSON upcasting |
|
|
38
|
+
| 3 | `aggregate.go` | DOMAIN | Handle(cmd)→[]Event, ApplyEvent, Snapshot |
|
|
39
|
+
| 4 | `aggregate_test.go` | DOMAIN | Pure tests (no I/O, no mocks) |
|
|
40
|
+
| 5 | `setup.go` | MECHANICAL | NewDomain wiring, ExecuteCommand adapter |
|
|
41
|
+
| 6 | `queries.go` | MECHANICAL | QueryHandler with read-through cache |
|
|
42
|
+
| 7 | `projections.go` | MECHANICAL | Stateless (event, DB) → updated projection row |
|
|
43
|
+
| 8 | `projection_consumer.go` | MECHANICAL | Routes own-domain events → projections |
|
|
44
|
+
| 9 | `kafka_consumer.go` | MECHANICAL | Cross-domain event → local command translation |
|
|
45
|
+
| 10 | `policies.go` | MECHANICAL | Pre-command DB validations + event-driven policies |
|
|
46
|
+
| 11 | `http_handler.go` | MECHANICAL | Gin handlers → ExecuteCommand or QueryHandler |
|
|
47
|
+
| 12 | `cmd/server/main.go` | MECHANICAL | Binary: cli.New() + runServer() |
|
|
48
|
+
| 13 | `migrations/001_create.sql` | MECHANICAL | Schema: events, snapshots, outbox, projections |
|
|
49
|
+
|
|
50
|
+
## Workflow
|
|
51
|
+
|
|
52
|
+
### Step 1: Create the module
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
mkdir -p domains/<name>/cmd/server domains/<name>/migrations
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Create `domains/<name>/go.mod`:
|
|
59
|
+
```
|
|
60
|
+
module github.com/company/ddd-monorepo/domains/<name>
|
|
61
|
+
|
|
62
|
+
go 1.22
|
|
63
|
+
|
|
64
|
+
require (
|
|
65
|
+
github.com/company/ddd-monorepo/pkg v0.0.0
|
|
66
|
+
github.com/gin-gonic/gin v1.9.0
|
|
67
|
+
github.com/google/uuid v1.6.0
|
|
68
|
+
github.com/lib/pq v1.10.9
|
|
69
|
+
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.53.0
|
|
70
|
+
go.opentelemetry.io/otel/trace v1.28.0
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
replace github.com/company/ddd-monorepo/pkg => ../../pkg
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Add to `go.work`:
|
|
77
|
+
```
|
|
78
|
+
use ./domains/<name>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Step 2: Commands
|
|
82
|
+
|
|
83
|
+
File: `domains/<name>/commands.go`
|
|
84
|
+
|
|
85
|
+
```go
|
|
86
|
+
package <name>
|
|
87
|
+
|
|
88
|
+
type Command interface {
|
|
89
|
+
is<Name>Command()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
type DoSomething struct {
|
|
93
|
+
ID string
|
|
94
|
+
Data string
|
|
95
|
+
}
|
|
96
|
+
func (DoSomething) is<Name>Command() {}
|
|
97
|
+
|
|
98
|
+
// Add idempotency notes: "returns nil, nil if already done"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Step 3: Events (with on-read upcasting)
|
|
102
|
+
|
|
103
|
+
File: `domains/<name>/events.go`
|
|
104
|
+
|
|
105
|
+
```go
|
|
106
|
+
package <name>
|
|
107
|
+
|
|
108
|
+
type Event interface {
|
|
109
|
+
eventstore.Event
|
|
110
|
+
is<Name>Event()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Always create V2 even for the first version — makes the upcasting
|
|
114
|
+
// path exist from day one. If you don't need V2 yet, it's identical
|
|
115
|
+
// to what V1 would have been.
|
|
116
|
+
type SomethingHappenedV2 struct {
|
|
117
|
+
ID string `json:"id"`
|
|
118
|
+
NewField string `json:"new_field"` // V2 addition
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
func (*SomethingHappenedV2) EventType() string { return "<name>.SomethingHappened" }
|
|
122
|
+
func (*SomethingHappenedV2) is<Name>Event() {}
|
|
123
|
+
|
|
124
|
+
func (e *SomethingHappenedV2) UnmarshalJSON(data []byte) error {
|
|
125
|
+
type v2 SomethingHappenedV2
|
|
126
|
+
if err := json.Unmarshal(data, (*v2)(e)); err == nil {
|
|
127
|
+
return nil
|
|
128
|
+
}
|
|
129
|
+
var v1 struct { ID string `json:"id"` }
|
|
130
|
+
if err := json.Unmarshal(data, &v1); err != nil {
|
|
131
|
+
return err
|
|
132
|
+
}
|
|
133
|
+
e.ID = v1.ID
|
|
134
|
+
e.NewField = "" // default for V1→V2
|
|
135
|
+
return nil
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Step 4: Aggregate (THE deep module)
|
|
140
|
+
|
|
141
|
+
File: `domains/<name>/aggregate.go`
|
|
142
|
+
|
|
143
|
+
This is where ALL business rules live. One entry point: `Handle(cmd)→[]Event`.
|
|
144
|
+
|
|
145
|
+
```go
|
|
146
|
+
package <name>
|
|
147
|
+
|
|
148
|
+
type <Name>State int
|
|
149
|
+
const (
|
|
150
|
+
StateDraft <Name>State = iota
|
|
151
|
+
StateDone
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
var (
|
|
155
|
+
ErrAlreadyDone = errors.New("<name>: already done")
|
|
156
|
+
ErrInvalidTransition = errors.New("<name>: invalid state transition")
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
type <Name> struct {
|
|
160
|
+
id string
|
|
161
|
+
version int
|
|
162
|
+
state <Name>State
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
var _ eventstore.Aggregate = (*<Name>)(nil)
|
|
166
|
+
|
|
167
|
+
func New<Name>(id string) *<Name> {
|
|
168
|
+
return &<Name>{id: id, state: StateDraft}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
func (a *<Name>) ID() string { return a.id }
|
|
172
|
+
func (a *<Name>) Version() int { return a.version }
|
|
173
|
+
|
|
174
|
+
// Handle is the ONLY write entry point. No other public mutators.
|
|
175
|
+
func (a *<Name>) Handle(cmd Command) ([]eventstore.Event, error) {
|
|
176
|
+
switch c := cmd.(type) {
|
|
177
|
+
case DoSomething:
|
|
178
|
+
return a.handleDoSomething(c)
|
|
179
|
+
default:
|
|
180
|
+
return nil, errors.New("<name>: unknown command type")
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
func (a *<Name>) handleDoSomething(cmd DoSomething) ([]eventstore.Event, error) {
|
|
185
|
+
if a.state != StateDraft {
|
|
186
|
+
return nil, ErrAlreadyDone
|
|
187
|
+
}
|
|
188
|
+
return []eventstore.Event{
|
|
189
|
+
&SomethingHappenedV2{ID: a.id, NewField: cmd.Data},
|
|
190
|
+
}, nil
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ApplyEvent rebuilds state. Called during Load and after Handle.
|
|
194
|
+
func (a *<Name>) ApplyEvent(e eventstore.Event) error {
|
|
195
|
+
switch e := e.(type) {
|
|
196
|
+
case *SomethingHappenedV2:
|
|
197
|
+
a.state = StateDone
|
|
198
|
+
a.version++
|
|
199
|
+
}
|
|
200
|
+
return nil
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Snapshot + ApplySnapshot (always implement, even if empty).
|
|
204
|
+
func (a *<Name>) ApplySnapshot(data []byte) error {
|
|
205
|
+
var snap <name>Snapshot
|
|
206
|
+
json.Unmarshal(data, &snap)
|
|
207
|
+
a.id = snap.ID
|
|
208
|
+
a.version = snap.Version
|
|
209
|
+
a.state = snap.State
|
|
210
|
+
return nil
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
func (a *<Name>) Snapshot() ([]byte, error) {
|
|
214
|
+
return json.Marshal(<name>Snapshot{ID: a.id, Version: a.version, State: a.state})
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Idempotency rule**: return `nil, nil` (not an error) for duplicate commands. The UoW short-circuits when events are empty.
|
|
219
|
+
|
|
220
|
+
### Step 5: Tests
|
|
221
|
+
|
|
222
|
+
File: `domains/<name>/aggregate_test.go`
|
|
223
|
+
|
|
224
|
+
Uses `github.com/stretchr/testify` — `require` for fatal assertions, `assert` for non-fatal.
|
|
225
|
+
|
|
226
|
+
Five test patterns to cover for every command:
|
|
227
|
+
1. Happy path: command → correct event type and fields
|
|
228
|
+
2. Invalid transition: wrong state → error
|
|
229
|
+
3. Idempotent: apply event, send same command → 0 events, no error
|
|
230
|
+
4. Event sourcing rebuild: apply N events → correct version
|
|
231
|
+
5. Snapshot round-trip: snapshot → rebuild → re-snapshot → identical
|
|
232
|
+
|
|
233
|
+
```go
|
|
234
|
+
package <name>_test
|
|
235
|
+
|
|
236
|
+
import (
|
|
237
|
+
"testing"
|
|
238
|
+
|
|
239
|
+
"github.com/stretchr/testify/assert"
|
|
240
|
+
"github.com/stretchr/testify/require"
|
|
241
|
+
|
|
242
|
+
"github.com/company/ddd-monorepo/domains/<name>"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
func TestDoSomething(t *testing.T) {
|
|
246
|
+
agg := <name>.New<Name>("id-1")
|
|
247
|
+
|
|
248
|
+
events, err := agg.Handle(<name>.DoSomething{ID: "id-1", Data: "hello"})
|
|
249
|
+
require.NoError(t, err)
|
|
250
|
+
require.Len(t, events, 1)
|
|
251
|
+
|
|
252
|
+
// Type check + cast pattern (IsType doesn't return the value).
|
|
253
|
+
require.IsType(t, &<name>.SomethingHappenedV2{}, events[0])
|
|
254
|
+
e := events[0].(*<name>.SomethingHappenedV2)
|
|
255
|
+
assert.Equal(t, "hello", e.NewField)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
func TestDoSomething_Idempotent(t *testing.T) {
|
|
259
|
+
agg := <name>.New<Name>("id-1")
|
|
260
|
+
|
|
261
|
+
_, err := agg.Handle(<name>.DoSomething{ID: "id-1"})
|
|
262
|
+
require.NoError(t, err)
|
|
263
|
+
require.NoError(t, agg.ApplyEvent(&<name>.SomethingHappenedV2{ID: "id-1"}))
|
|
264
|
+
|
|
265
|
+
events, err := agg.Handle(<name>.DoSomething{ID: "id-1"})
|
|
266
|
+
require.NoError(t, err)
|
|
267
|
+
assert.Empty(t, events)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
func TestDoSomething_InvalidTransition(t *testing.T) {
|
|
271
|
+
agg := <name>.New<Name>("id-1")
|
|
272
|
+
require.NoError(t, agg.ApplyEvent(&<name>.SomethingHappenedV2{ID: "id-1"}))
|
|
273
|
+
|
|
274
|
+
_, err := agg.Handle(<name>.DoSomething{ID: "id-1"})
|
|
275
|
+
require.Error(t, err)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
func TestEventSourcing_RebuildState(t *testing.T) {
|
|
279
|
+
agg := <name>.New<Name>("id-1")
|
|
280
|
+
require.NoError(t, agg.ApplyEvent(&<name>.SomethingHappenedV2{ID: "id-1"}))
|
|
281
|
+
assert.Equal(t, 1, agg.Version())
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
func TestSnapshot_RoundTrip(t *testing.T) {
|
|
285
|
+
agg := <name>.New<Name>("id-1")
|
|
286
|
+
require.NoError(t, agg.ApplyEvent(&<name>.SomethingHappenedV2{ID: "id-1"}))
|
|
287
|
+
|
|
288
|
+
data, err := agg.Snapshot()
|
|
289
|
+
require.NoError(t, err)
|
|
290
|
+
|
|
291
|
+
agg2 := <name>.New<Name>("id-1")
|
|
292
|
+
require.NoError(t, agg2.ApplySnapshot(data))
|
|
293
|
+
assert.Equal(t, 1, agg2.Version())
|
|
294
|
+
|
|
295
|
+
data2, err := agg2.Snapshot()
|
|
296
|
+
require.NoError(t, err)
|
|
297
|
+
assert.Equal(t, string(data), string(data2))
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Step 6: Domain wiring
|
|
302
|
+
|
|
303
|
+
File: `domains/<name>/setup.go`
|
|
304
|
+
|
|
305
|
+
```go
|
|
306
|
+
package <name>
|
|
307
|
+
|
|
308
|
+
type Domain struct {
|
|
309
|
+
DB *sql.DB
|
|
310
|
+
Store eventstore.EventStore
|
|
311
|
+
Outbox outbox.Outbox
|
|
312
|
+
Registry eventstore.Registry
|
|
313
|
+
Poller *messaging.Poller
|
|
314
|
+
QueryHandler *QueryHandler
|
|
315
|
+
UoW *uow.UnitOfWork
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
func NewDomain(db *sql.DB, kafka messaging.Publisher, c cache.Cache, tracer trace.Tracer) *Domain {
|
|
319
|
+
d := &Domain{DB: db, QueryHandler: &QueryHandler{DB: db, Cache: c}}
|
|
320
|
+
|
|
321
|
+
d.Registry = eventstore.Registry{
|
|
322
|
+
"<name>.SomethingHappened": func() eventstore.Event { return &SomethingHappenedV2{} },
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
baseStore := eventstore.NewPostgres(db, d.Registry, eventstore.Config{SnapshotFrequency: 100})
|
|
326
|
+
d.Store = eventstore.NewTracingStore(baseStore, tracer)
|
|
327
|
+
d.Outbox = outbox.NewPostgres(db)
|
|
328
|
+
tracedKafka := messaging.NewTracingPublisher(kafka, tracer)
|
|
329
|
+
d.Poller = messaging.NewPoller(db, tracedKafka)
|
|
330
|
+
d.UoW = uow.New(db, d.Store, d.Outbox, tracer)
|
|
331
|
+
return d
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ExecuteCommand is a THIN adapter. All complexity is in uow.Execute.
|
|
335
|
+
func (d *Domain) ExecuteCommand(ctx context.Context, cmd Command) ([]eventstore.Event, error) {
|
|
336
|
+
streamID := extractStreamID(cmd)
|
|
337
|
+
return d.UoW.Execute(ctx, streamID,
|
|
338
|
+
func() eventstore.Aggregate { return New<Name>(streamID) },
|
|
339
|
+
func(agg eventstore.Aggregate) ([]eventstore.Event, error) {
|
|
340
|
+
return agg.(*<Name>).Handle(cmd)
|
|
341
|
+
},
|
|
342
|
+
func(events []eventstore.Event) ([]outbox.Record, error) {
|
|
343
|
+
return d.eventsToOutboxRecords(events), nil
|
|
344
|
+
},
|
|
345
|
+
)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// streamIDer interface: implement on every command and event.
|
|
349
|
+
type streamIDer interface { streamID() string }
|
|
350
|
+
|
|
351
|
+
func (c DoSomething) streamID() string { return c.ID }
|
|
352
|
+
// ... repeat for every command and event
|
|
353
|
+
|
|
354
|
+
func extractStreamID(cmd Command) string {
|
|
355
|
+
if s, ok := cmd.(streamIDer); ok { return s.streamID() }
|
|
356
|
+
return ""
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
func (d *Domain) eventsToOutboxRecords(events []eventstore.Event) []outbox.Record {
|
|
360
|
+
// Marshal event → outbox.Record with Topic=e.EventType(), Key=u.UUID, Payload=json
|
|
361
|
+
// Copy the pattern from domains/ordering/setup.go
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Step 7: Projections + Queries
|
|
366
|
+
|
|
367
|
+
**Projections** (`projections.go`): stateless (event, DB) → updated row. One method per event type.
|
|
368
|
+
|
|
369
|
+
```go
|
|
370
|
+
type <Name>Projection struct { DB *sql.DB }
|
|
371
|
+
|
|
372
|
+
func (p *<Name>Projection) OnSomethingHappened(ctx context.Context, msg messaging.Message) error {
|
|
373
|
+
var e SomethingHappenedV2
|
|
374
|
+
json.Unmarshal(msg.Payload, &e)
|
|
375
|
+
_, err := p.DB.ExecContext(ctx,
|
|
376
|
+
`INSERT INTO <name>_summaries (id, data, updated_at)
|
|
377
|
+
VALUES ($1, $2, now()) ON CONFLICT (id) DO UPDATE SET data = $2, updated_at = now()`,
|
|
378
|
+
e.ID, e.NewField)
|
|
379
|
+
return err
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**Queries** (`queries.go`): read from projection tables with read-through cache.
|
|
384
|
+
|
|
385
|
+
```go
|
|
386
|
+
type QueryHandler struct {
|
|
387
|
+
DB *sql.DB
|
|
388
|
+
Cache cache.Cache
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
func (q *QueryHandler) GetByID(ctx context.Context, id string) (*Summary, error) {
|
|
392
|
+
// 1. Cache hit? return
|
|
393
|
+
// 2. Cache miss → DB query → cache set → return
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Step 8: Consumers
|
|
398
|
+
|
|
399
|
+
**ProjectionConsumer** (`projection_consumer.go`): consumes OWN domain's topic, routes to projections.
|
|
400
|
+
|
|
401
|
+
```go
|
|
402
|
+
type ProjectionConsumer struct { domain *Domain; proj *<Name>Projection }
|
|
403
|
+
|
|
404
|
+
func (c *ProjectionConsumer) Handle(ctx context.Context, msg messaging.Message) error {
|
|
405
|
+
switch msg.Headers["event_type"] {
|
|
406
|
+
case "<name>.SomethingHappened":
|
|
407
|
+
c.proj.OnSomethingHappened(ctx, msg)
|
|
408
|
+
return c.invalidateCache(ctx, msg)
|
|
409
|
+
}
|
|
410
|
+
return nil
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
**KafkaEventConsumer** (`kafka_consumer.go`): consumes OTHER domains' topics, translates to local commands.
|
|
415
|
+
|
|
416
|
+
```go
|
|
417
|
+
func (c *KafkaEventConsumer) HandleOtherDomainEvent(ctx context.Context, msg messaging.Message) error {
|
|
418
|
+
switch msg.Headers["event_type"] {
|
|
419
|
+
case "otherdomain.EventName":
|
|
420
|
+
var event struct { ID string `json:"id"` }
|
|
421
|
+
json.Unmarshal(msg.Payload, &event)
|
|
422
|
+
_, err := c.domain.ExecuteCommand(ctx, DoSomething{ID: event.ID})
|
|
423
|
+
return err
|
|
424
|
+
}
|
|
425
|
+
return nil
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Step 9: HTTP handlers
|
|
430
|
+
|
|
431
|
+
File: `domains/<name>/http_handler.go`
|
|
432
|
+
|
|
433
|
+
Gin handlers that call ExecuteCommand (write) or QueryHandler (read). Map domain errors to HTTP status codes (422, 409, 500). See `domains/ordering/http_handler.go` for the exact pattern.
|
|
434
|
+
|
|
435
|
+
### Step 10: main.go
|
|
436
|
+
|
|
437
|
+
File: `domains/<name>/cmd/server/main.go`
|
|
438
|
+
|
|
439
|
+
```go
|
|
440
|
+
package main
|
|
441
|
+
|
|
442
|
+
import (
|
|
443
|
+
"context"
|
|
444
|
+
"github.com/company/ddd-monorepo/domains/<name>"
|
|
445
|
+
"github.com/company/ddd-monorepo/pkg/cli"
|
|
446
|
+
"github.com/company/ddd-monorepo/pkg/messaging"
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
func main() {
|
|
450
|
+
cli.New(cli.Opts{
|
|
451
|
+
Name: "<name>",
|
|
452
|
+
Description: "<Name> domain service",
|
|
453
|
+
MigrationsDir: "domains/<name>/migrations",
|
|
454
|
+
Serve: runServer,
|
|
455
|
+
}).Run()
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
func runServer(ctx context.Context, deps cli.Dependencies) error {
|
|
459
|
+
domain := <name>.NewDomain(deps.DB, deps.Kafka, nil, deps.Tracer)
|
|
460
|
+
|
|
461
|
+
go func() { domain.Poller.Start(ctx) }()
|
|
462
|
+
|
|
463
|
+
// Own-domain projection consumer
|
|
464
|
+
projConsumer := messaging.NewKafkaConsumer(deps.Config.KafkaBrokers, "<name>.events", "<name>-projections")
|
|
465
|
+
tracedProj := messaging.NewTracedConsumer(projConsumer, deps.Tracer)
|
|
466
|
+
projHandler := <name>.NewProjectionConsumer(domain)
|
|
467
|
+
go func() { tracedProj.Consume(ctx, projHandler.Handle) }()
|
|
468
|
+
|
|
469
|
+
// Cross-domain consumers (one per external domain)
|
|
470
|
+
|
|
471
|
+
// Gin HTTP server with otelgin middleware
|
|
472
|
+
|
|
473
|
+
<-ctx.Done()
|
|
474
|
+
// Graceful shutdown
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Step 11: Migration
|
|
479
|
+
|
|
480
|
+
File: `domains/<name>/migrations/001_create_<name>.sql`
|
|
481
|
+
|
|
482
|
+
```sql
|
|
483
|
+
CREATE TABLE IF NOT EXISTS events (...) -- same schema as other domains
|
|
484
|
+
CREATE TABLE IF NOT EXISTS snapshots (...)
|
|
485
|
+
CREATE TABLE IF NOT EXISTS outbox (...)
|
|
486
|
+
CREATE TABLE IF NOT EXISTS <name>_summaries (...) -- domain-specific projection
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
Copy the DDL from `domains/ordering/migrations/` or `domains/inventory/migrations/001_create_inventory.sql`. The events, snapshots, and outbox tables are identical across domains.
|
|
490
|
+
|
|
491
|
+
### Step 12: Add to Makefile
|
|
492
|
+
|
|
493
|
+
```
|
|
494
|
+
run-<name>:
|
|
495
|
+
cd domains/<name> && ... go run ./cmd/server serve
|
|
496
|
+
|
|
497
|
+
migrate-<name>:
|
|
498
|
+
cd domains/<name> && ... go run ./cmd/server migrate --dir migrations
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
## Cross-Domain Communication Pattern
|
|
502
|
+
|
|
503
|
+
```
|
|
504
|
+
Domain A emits events → outbox table → Poller → Kafka topic "A.events"
|
|
505
|
+
→ Domain B's KafkaEventConsumer.HandleAEvent()
|
|
506
|
+
→ deserialize foreign event → translate to local command
|
|
507
|
+
→ domain.ExecuteCommand → aggregate.Handle → event store + outbox
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
The consumer is a pure adapter: deserialize → map → ExecuteCommand. No business logic.
|
|
511
|
+
|
|
512
|
+
## Wiring Order in main.go (INVARIANT)
|
|
513
|
+
|
|
514
|
+
The `cli` package handles this via `runServe`. If bypassing cli, the order is:
|
|
515
|
+
1. telemetry.Init
|
|
516
|
+
2. db.Register + sql.Open
|
|
517
|
+
3. messaging.NewKafkaPublisher
|
|
518
|
+
4. domain.NewDomain
|
|
519
|
+
5. Poller.Start (goroutine)
|
|
520
|
+
6. Own-domain projection consumer (goroutine)
|
|
521
|
+
7. Cross-domain consumers (goroutines)
|
|
522
|
+
8. Policies consumer (goroutine)
|
|
523
|
+
9. Gin HTTP server with otelgin middleware
|
|
524
|
+
10. signal.NotifyContext → graceful shutdown
|
|
525
|
+
|
|
526
|
+
## Guardrails
|
|
527
|
+
|
|
528
|
+
- **No business logic outside the aggregate**. If you write `if state == ...` in a projection, policy, or handler, move it to `aggregate.go`.
|
|
529
|
+
- **ExecuteCommand is always a thin adapter**. It delegates to `uow.Execute`. Never put tx logic there.
|
|
530
|
+
- **Projections are stateless**. `(event, DB) → updated row`. No if-else on business state.
|
|
531
|
+
- **Idempotency returns nil, nil**. Never return an error for a duplicate command.
|
|
532
|
+
- **Every event type must be in the Registry**. Missing types cause "unknown event type" at runtime.
|
|
533
|
+
- **Snapshot + ApplySnapshot always implemented**. Even if empty initially.
|
|
534
|
+
- **Upcasting on read**. Always deserialize into the latest version (V2). V1→V2 in UnmarshalJSON.
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: code-review
|
|
3
|
-
description: Review changes since a fixed point (commit, branch, or tag) along two axes — Standards (does the code follow this repo's conventions?) and Spec (does it match the originating issue/PRD?). Use when reviewing a branch, PR, or work-in-progress changes.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
Review changes between `HEAD` and a fixed point the user supplies.
|
|
7
|
-
|
|
8
|
-
## Process
|
|
9
|
-
|
|
10
|
-
1. **Pin the fixed point** — ask the user for a commit SHA, branch, tag, or `main`. Resolve it and confirm the diff is non-empty.
|
|
11
|
-
|
|
12
|
-
2. **Identify the spec** — look for issue references in commit messages (`#123`, `Closes #45`), PRD files under `docs/` or `specs/`, or ask the user.
|
|
13
|
-
|
|
14
|
-
3. **Review on two axes in parallel:**
|
|
15
|
-
- **Standards** — does the diff follow documented coding conventions?
|
|
16
|
-
- **Spec** — does it implement what was asked?
|
|
17
|
-
|
|
18
|
-
Run both as sub-agents so they don't pollute each other's context.
|
|
19
|
-
|
|
20
|
-
4. **Report** — present findings under `## Standards` and `## Spec` headings. Keep axes separate — a change can pass one and fail the other.
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: diagnosing-bugs
|
|
3
|
-
description: Systematic diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose," "debug this," or reports something broken, throwing, failing, or slow.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
## Diagnosis Loop
|
|
7
|
-
|
|
8
|
-
Follow this cycle until the root cause is identified:
|
|
9
|
-
|
|
10
|
-
### 1. Reproduce
|
|
11
|
-
- Confirm the bug is reproducible.
|
|
12
|
-
- Find the minimal reproduction steps.
|
|
13
|
-
- Note the exact observed vs expected behavior.
|
|
14
|
-
|
|
15
|
-
### 2. Isolate
|
|
16
|
-
- Bisect the codebase — when did this start?
|
|
17
|
-
- Narrow the scope: which module, function, or line?
|
|
18
|
-
- Use `git bisect` for regressions.
|
|
19
|
-
|
|
20
|
-
### 3. Hypothesize
|
|
21
|
-
- Propose a specific root-cause hypothesis.
|
|
22
|
-
- Design a test or probe that would confirm or refute it.
|
|
23
|
-
|
|
24
|
-
### 4. Test
|
|
25
|
-
- Run the probe (log, breakpoint, test).
|
|
26
|
-
- If confirmed → fix. If refuted → return to step 3.
|
|
27
|
-
|
|
28
|
-
### Guidelines
|
|
29
|
-
- **One hypothesis at a time.** Don't shotgun-debug.
|
|
30
|
-
- **Read the error message** — it often contains the answer.
|
|
31
|
-
- **Check recent changes first** — most bugs come from the last change.
|
|
32
|
-
- **Don't guess and check** — each probe should be deliberate.
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: tdd
|
|
3
|
-
description: Test-driven development loop. Write a failing test first, then implement the minimal code to make it pass, then refactor. Use when building features or fixing bugs test-first, or when the user mentions "red-green-refactor".
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
## Red-Green-Refactor Loop
|
|
7
|
-
|
|
8
|
-
### 1. Red — Write a failing test
|
|
9
|
-
- Write the smallest test that captures the desired behavior.
|
|
10
|
-
- The test must fail before any implementation code exists.
|
|
11
|
-
- Use the project's existing test framework and conventions.
|
|
12
|
-
|
|
13
|
-
### 2. Green — Make it pass
|
|
14
|
-
- Write the **minimum** code to make the test pass.
|
|
15
|
-
- Don't over-engineer — no refactoring, no "future-proofing."
|
|
16
|
-
- If the test still fails, diagnose and fix the implementation.
|
|
17
|
-
|
|
18
|
-
### 3. Refactor
|
|
19
|
-
- Clean up both test and production code.
|
|
20
|
-
- Remove duplication, improve names, extract helpers.
|
|
21
|
-
- Run tests to confirm nothing broke.
|
|
22
|
-
|
|
23
|
-
### Guidelines
|
|
24
|
-
- **Test behavior, not implementation.** Avoid coupling tests to internal details.
|
|
25
|
-
- **One assertion per test** where practical.
|
|
26
|
-
- **Tests are documentation** — name them clearly.
|
|
27
|
-
- **Fast feedback** — keep tests quick; mock external dependencies.
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: grilling
|
|
3
|
-
description: Grill the user relentlessly about a plan, decision, or idea to stress-test their thinking. Use when the user wants to pressure-test an idea, or mentions "grill me," "stress test," or "poke holes in this."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
## Grilling Session
|
|
7
|
-
|
|
8
|
-
The goal is to find flaws, gaps, and unstated assumptions in the user's plan — not to be contrarian but to make the plan stronger.
|
|
9
|
-
|
|
10
|
-
### Rules
|
|
11
|
-
1. **Ask one question at a time.** Let the user answer before moving to the next.
|
|
12
|
-
2. **Probe the edges.** What happens at scale? Under failure? With bad data?
|
|
13
|
-
3. **Challenge assumptions.** "What makes you sure that...?"
|
|
14
|
-
4. **Surface trade-offs.** "If you optimize for X, what happens to Y?"
|
|
15
|
-
5. **Don't offer solutions yet.** The grilling phase is for finding problems.
|
|
16
|
-
|
|
17
|
-
### Areas to probe
|
|
18
|
-
- **Scope:** What's in and out? What's the MVP vs nice-to-have?
|
|
19
|
-
- **Failure modes:** What breaks? What's the rollback plan?
|
|
20
|
-
- **Dependencies:** What does this depend on? What depends on this?
|
|
21
|
-
- **Alternatives:** What else did you consider? Why was this chosen?
|
|
22
|
-
- **Success criteria:** How will you know it worked?
|
|
23
|
-
|
|
24
|
-
### End condition
|
|
25
|
-
Stop when the user says "that's enough" or when you've asked 5-8 questions without finding new gaps.
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: handoff
|
|
3
|
-
description: Create a structured handoff document summarizing current work, decisions made, and next steps. Use when the user wants to hand off work to another developer or agent, or save context before switching tasks.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
## Handoff Document
|
|
7
|
-
|
|
8
|
-
Create a concise document that captures everything the next person (human or agent) needs to continue the work.
|
|
9
|
-
|
|
10
|
-
### Structure
|
|
11
|
-
|
|
12
|
-
1. **What we're building** — one sentence on the goal.
|
|
13
|
-
2. **Current state** — what's done, what's in progress, what's blocked.
|
|
14
|
-
3. **Key decisions** — what was chosen, why, and what alternatives were considered.
|
|
15
|
-
4. **Files to look at** — the 3-5 most important files.
|
|
16
|
-
5. **Next steps** — the immediate next action, clearly scoped.
|
|
17
|
-
6. **Gotchas** — surprising constraints, workarounds, or known issues.
|
|
18
|
-
|
|
19
|
-
### Guidelines
|
|
20
|
-
- Write for someone with context about the project but not this specific task.
|
|
21
|
-
- Be honest about what's messy or incomplete.
|
|
22
|
-
- Don't bury the lede — put the most important info first.
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: prototype
|
|
3
|
-
description: Build a throwaway prototype to answer a design question. Use when the user wants to sanity-check whether a state model or logic feels right, or explore what a UI should look like.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
## Throwaway Prototyping
|
|
7
|
-
|
|
8
|
-
The prototype exists to answer **one question**. It should be built as fast as possible and thrown away after the question is answered.
|
|
9
|
-
|
|
10
|
-
### 1. Define the question
|
|
11
|
-
- What exactly are we trying to learn?
|
|
12
|
-
- What would a "yes" or "no" answer look like?
|
|
13
|
-
|
|
14
|
-
### 2. Build the minimum
|
|
15
|
-
- Only the code needed to answer the question.
|
|
16
|
-
- No error handling, no tests, no polish.
|
|
17
|
-
- Fake anything that isn't the thing being tested.
|
|
18
|
-
|
|
19
|
-
### 3. Run the experiment
|
|
20
|
-
- Exercise the prototype with the specific scenario.
|
|
21
|
-
- Record what happened.
|
|
22
|
-
|
|
23
|
-
### 4. Decide
|
|
24
|
-
- Question answered? → Report findings, throw away code.
|
|
25
|
-
- Question not answered? → Refine the prototype or the question.
|
|
26
|
-
|
|
27
|
-
### Red flags (abort)
|
|
28
|
-
- "Let me just add one more feature..."
|
|
29
|
-
- "This prototype is actually pretty good, maybe we should..."
|
|
30
|
-
- More than 30 minutes spent on the prototype.
|
|
31
|
-
|
|
32
|
-
**Reminder:** The prototype is garbage by design. The value is the learning, not the code.
|