blacksmith-cli 0.1.4 → 0.1.6

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.
Files changed (32) hide show
  1. package/dist/index.js +276 -20
  2. package/dist/index.js.map +1 -1
  3. package/package.json +1 -1
  4. package/src/templates/backend/utils/__init__.py.hbs +0 -0
  5. package/src/templates/backend/utils/models.py.hbs +11 -0
  6. package/src/templates/frontend/package.json.hbs +9 -1
  7. package/src/templates/frontend/src/__tests__/setup.ts.hbs +21 -0
  8. package/src/templates/frontend/src/__tests__/test-utils.tsx.hbs +80 -0
  9. package/src/templates/frontend/src/pages/home/home.tsx.hbs +93 -11
  10. package/src/templates/frontend/tsconfig.app.json.hbs +1 -0
  11. package/src/templates/frontend/vite.config.ts.hbs +8 -0
  12. package/src/templates/resource/api-hooks/index.ts.hbs +2 -0
  13. package/src/templates/resource/{frontend/hooks → api-hooks}/use-{{kebabs}}-query.ts.hbs +10 -2
  14. package/src/templates/resource/{frontend/hooks → api-hooks}/use-{{kebab}}-mutations.ts.hbs +1 -1
  15. package/src/templates/resource/backend/models.py.hbs +2 -3
  16. package/src/templates/resource/frontend/components/{{kebab}}-card.tsx.hbs +1 -1
  17. package/src/templates/resource/frontend/components/{{kebab}}-list.tsx.hbs +1 -1
  18. package/src/templates/resource/frontend/index.ts.hbs +1 -2
  19. package/src/templates/resource/frontend/pages/{{kebabs}}-page.tsx.hbs +1 -1
  20. package/src/templates/resource/frontend/pages/{{kebab}}-detail-page.tsx.hbs +3 -11
  21. package/src/templates/resource/pages/components/{{kebab}}-card.tsx.hbs +1 -1
  22. package/src/templates/resource/pages/components/{{kebab}}-list.tsx.hbs +1 -1
  23. package/src/templates/resource/pages/hooks/index.ts.hbs +9 -0
  24. package/src/templates/resource/pages/index.ts.hbs +1 -2
  25. package/src/templates/resource/pages/{{kebabs}}-page.tsx.hbs +1 -1
  26. package/src/templates/resource/pages/{{kebab}}-detail-page.tsx.hbs +3 -11
  27. package/src/templates/frontend/src/pages/home/components/features-grid.tsx.hbs +0 -88
  28. package/src/templates/frontend/src/pages/home/components/getting-started.tsx.hbs +0 -88
  29. package/src/templates/frontend/src/pages/home/components/hero-section.tsx.hbs +0 -47
  30. package/src/templates/frontend/src/pages/home/components/resources-section.tsx.hbs +0 -34
  31. package/src/templates/resource/pages/hooks/use-{{kebabs}}-query.ts.hbs +0 -35
  32. package/src/templates/resource/pages/hooks/use-{{kebab}}-mutations.ts.hbs +0 -39
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/utils/logger.ts","../src/commands/init.ts","../src/utils/template.ts","../src/utils/exec.ts","../src/utils/paths.ts","../src/commands/ai-setup.ts","../src/skills/core-rules.ts","../src/skills/project-overview.ts","../src/skills/django.ts","../src/skills/django-rest-advanced.ts","../src/skills/api-documentation.ts","../src/skills/react.ts","../src/skills/react-query.ts","../src/skills/page-structure.ts","../src/skills/blacksmith-ui-react.ts","../src/skills/blacksmith-ui-forms.ts","../src/skills/blacksmith-ui-auth.ts","../src/skills/blacksmith-hooks.ts","../src/skills/blacksmith-cli.ts","../src/skills/ui-design.ts","../src/skills/programming-paradigms.ts","../src/skills/clean-code.ts","../src/skills/ai-guidelines.ts","../src/commands/dev.ts","../src/commands/sync.ts","../src/commands/make-resource.ts","../src/utils/names.ts","../src/commands/build.ts","../src/commands/eject.ts","../src/commands/skills.ts","../src/commands/backend.ts","../src/commands/frontend.ts"],"sourcesContent":["import { Command } from 'commander'\nimport { banner } from './utils/logger.js'\nimport { init } from './commands/init.js'\nimport { dev } from './commands/dev.js'\nimport { sync } from './commands/sync.js'\nimport { makeResource } from './commands/make-resource.js'\nimport { build } from './commands/build.js'\nimport { eject } from './commands/eject.js'\nimport { setupSkills, listSkills } from './commands/skills.js'\nimport { backend } from './commands/backend.js'\nimport { frontend } from './commands/frontend.js'\n\nconst program = new Command()\n\nprogram\n .name('blacksmith')\n .description('Fullstack Django + React framework')\n .version('0.1.0')\n .hook('preAction', () => {\n banner()\n })\n\nprogram\n .command('init')\n .argument('[name]', 'Project name')\n .option('--ai', 'Set up AI development skills and documentation (CLAUDE.md)')\n .option('--no-blacksmith-ui-skill', 'Disable blacksmith-ui skill when using --ai')\n .option('-b, --backend-port <port>', 'Django backend port (default: 8000)')\n .option('-f, --frontend-port <port>', 'Vite frontend port (default: 5173)')\n .option('-t, --theme-color <color>', 'Theme color (zinc, slate, blue, green, orange, red, violet)')\n .description('Create a new Blacksmith project')\n .action(init)\n\nprogram\n .command('dev')\n .description('Start development servers (Django + Vite + OpenAPI sync)')\n .action(dev)\n\nprogram\n .command('sync')\n .description('Sync OpenAPI schema to frontend types, schemas, and hooks')\n .action(sync)\n\nprogram\n .command('make:resource')\n .argument('<name>', 'Resource name (PascalCase, e.g. BlogPost)')\n .description('Create a new resource (model, serializer, viewset, hooks, pages)')\n .action(makeResource)\n\nprogram\n .command('build')\n .description('Build both frontend and backend for production')\n .action(build)\n\nprogram\n .command('eject')\n .description('Remove Blacksmith, keep a clean Django + React project')\n .action(eject)\n\nprogram\n .command('setup:ai')\n .description('Generate CLAUDE.md with AI development skills for the project')\n .option('--no-blacksmith-ui-skill', 'Exclude blacksmith-ui skill')\n .action(setupSkills)\n\nprogram\n .command('skills')\n .description('List all available AI development skills')\n .action(listSkills)\n\nprogram\n .command('backend')\n .argument('[args...]', 'Django management command and arguments')\n .description('Run a Django management command (e.g. blacksmith backend createsuperuser)')\n .allowUnknownOption()\n .action(backend)\n\nprogram\n .command('frontend')\n .argument('[args...]', 'npm command and arguments')\n .description('Run an npm command in the frontend (e.g. blacksmith frontend install axios)')\n .allowUnknownOption()\n .action(frontend)\n\nprogram.parse()\n","import chalk from 'chalk'\nimport ora, { type Ora } from 'ora'\nimport { createInterface } from 'node:readline'\n\nexport const log = {\n info: (msg: string) => console.log(chalk.blue('ℹ'), msg),\n success: (msg: string) => console.log(chalk.green('✓'), msg),\n warn: (msg: string) => console.log(chalk.yellow('⚠'), msg),\n error: (msg: string) => console.log(chalk.red('✗'), msg),\n step: (msg: string) => console.log(chalk.cyan('→'), msg),\n blank: () => console.log(),\n}\n\nexport function promptText(label: string, defaultValue?: string): Promise<string> {\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n const def = defaultValue ? chalk.dim(` (${defaultValue})`) : ''\n const question = ` ${chalk.cyan('?')} ${chalk.bold(label)}${def}${chalk.dim(':')} `\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close()\n resolve(answer.trim() || defaultValue || '')\n })\n })\n}\n\nexport function promptYesNo(label: string, defaultValue = false): Promise<boolean> {\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n const hint = defaultValue ? chalk.dim(' (Y/n)') : chalk.dim(' (y/N)')\n const question = ` ${chalk.cyan('?')} ${chalk.bold(label)}${hint}${chalk.dim(':')} `\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close()\n const val = answer.trim().toLowerCase()\n if (!val) return resolve(defaultValue)\n resolve(['y', 'yes'].includes(val))\n })\n })\n}\n\nexport function promptSelect(label: string, options: string[], defaultValue?: string): Promise<string> {\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n const optionList = options.map((opt, i) => `${chalk.dim(` ${i + 1}.`)} ${opt}`).join('\\n')\n const def = defaultValue ? chalk.dim(` (${defaultValue})`) : ''\n const question = ` ${chalk.cyan('?')} ${chalk.bold(label)}${def}\\n${optionList}\\n ${chalk.dim('Choice:')} `\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close()\n const trimmed = answer.trim()\n if (!trimmed && defaultValue) return resolve(defaultValue)\n const index = parseInt(trimmed, 10)\n if (index >= 1 && index <= options.length) return resolve(options[index - 1])\n const match = options.find((opt) => opt.toLowerCase() === trimmed.toLowerCase())\n resolve(match || defaultValue || options[0])\n })\n })\n}\n\nexport function printConfig(config: Record<string, string>) {\n const bar = chalk.dim('│')\n console.log()\n console.log(` ${chalk.dim('┌──────────────────────────────────────┐')}`)\n console.log(` ${bar} ${chalk.bold.white('Configuration')}${' '.repeat(23)}${bar}`)\n console.log(` ${chalk.dim('├──────────────────────────────────────┤')}`)\n for (const [key, value] of Object.entries(config)) {\n const padded = `${chalk.dim(key + ':')} ${chalk.white(value)}`\n const rawLen = `${key}: ${value}`.length\n const padding = ' '.repeat(Math.max(1, 36 - rawLen))\n console.log(` ${bar} ${padded}${padding}${bar}`)\n }\n console.log(` ${chalk.dim('└──────────────────────────────────────┘')}`)\n console.log()\n}\n\nexport function spinner(text: string): Ora {\n return ora({ text, color: 'cyan' }).start()\n}\n\nexport function banner() {\n const logo = [\n ' ██████╗ ██╗ █████╗ ██████╗██╗ ██╗',\n ' ██╔══██╗██║ ██╔══██╗██╔════╝██║ ██╔╝',\n ' ██████╔╝██║ ███████║██║ █████╔╝ ',\n ' ██╔══██╗██║ ██╔══██║██║ ██╔═██╗ ',\n ' ██████╔╝███████╗██║ ██║╚██████╗██║ ██╗',\n ' ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝',\n ' ███████╗███╗ ███╗██╗████████╗██╗ ██╗',\n ' ██╔════╝████╗ ████║██║╚══██╔══╝██║ ██║',\n ' ███████╗██╔████╔██║██║ ██║ ███████║',\n ' ╚════██║██║╚██╔╝██║██║ ██║ ██╔══██║',\n ' ███████║██║ ╚═╝ ██║██║ ██║ ██║ ██║',\n ' ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝',\n ]\n\n console.log()\n for (const line of logo) {\n console.log(chalk.cyan(line))\n }\n console.log()\n console.log(chalk.dim(' Welcome to Blacksmith — forge fullstack apps with one command.'))\n console.log()\n}\n\nexport function printNextSteps(projectName: string, backendPort = 8000, frontendPort = 5173) {\n log.blank()\n log.success('Project created successfully!')\n log.blank()\n console.log(chalk.bold(' Next steps:'))\n console.log()\n console.log(` ${chalk.cyan('cd')} ${projectName}`)\n console.log(` ${chalk.cyan('blacksmith dev')} ${chalk.dim('# Start development servers')}`)\n console.log()\n console.log(chalk.dim(` Django: http://localhost:${backendPort}`))\n console.log(chalk.dim(` React: http://localhost:${frontendPort}`))\n console.log(chalk.dim(` Swagger: http://localhost:${backendPort}/api/docs/`))\n console.log(chalk.dim(` ReDoc: http://localhost:${backendPort}/api/redoc/`))\n log.blank()\n}\n","import path from 'node:path'\nimport fs from 'node:fs'\nimport { spawn } from 'node:child_process'\nimport { renderDirectory } from '../utils/template.js'\nimport { exec, execPython, execPip, commandExists } from '../utils/exec.js'\nimport { getTemplatesDir } from '../utils/paths.js'\nimport { log, spinner, printNextSteps, promptText, promptYesNo, promptSelect, printConfig } from '../utils/logger.js'\nimport { setupAiDev } from './ai-setup.js'\n\nfunction parsePort(value: string, label: string): number {\n const port = parseInt(value, 10)\n if (isNaN(port) || port < 1 || port > 65535) {\n log.error(`Invalid ${label} port: ${value}`)\n process.exit(1)\n }\n return port\n}\n\nconst THEME_PRESETS = ['default', 'blue', 'green', 'violet', 'red', 'neutral']\n\ninterface InitOptions {\n ai?: boolean\n blacksmithUiSkill?: boolean\n backendPort?: string\n frontendPort?: string\n themeColor?: string\n}\n\nexport async function init(name: string | undefined, options: InitOptions) {\n // Interactive prompts for values not provided via flags\n if (!name) {\n name = await promptText('Project name')\n if (!name) {\n log.error('Project name is required.')\n process.exit(1)\n }\n }\n\n if (!options.backendPort) {\n options.backendPort = await promptText('Backend port', '8000')\n }\n\n if (!options.frontendPort) {\n options.frontendPort = await promptText('Frontend port', '5173')\n }\n\n if (!options.themeColor) {\n options.themeColor = await promptSelect('Theme preset', THEME_PRESETS, 'default')\n }\n\n if (options.ai === undefined) {\n options.ai = await promptYesNo('Set up AI coding support')\n }\n\n const backendPort = parsePort(options.backendPort, 'backend')\n const frontendPort = parsePort(options.frontendPort, 'frontend')\n const themePreset = THEME_PRESETS.includes(options.themeColor) ? options.themeColor : 'default'\n\n printConfig({\n 'Project': name,\n 'Backend': `Django on :${backendPort}`,\n 'Frontend': `React on :${frontendPort}`,\n 'Theme': themePreset,\n 'AI support': options.ai ? 'Yes' : 'No',\n })\n\n const projectDir = path.resolve(process.cwd(), name)\n const backendDir = path.join(projectDir, 'backend')\n const frontendDir = path.join(projectDir, 'frontend')\n const templatesDir = getTemplatesDir()\n\n // Validate\n if (fs.existsSync(projectDir)) {\n log.error(`Directory \"${name}\" already exists.`)\n process.exit(1)\n }\n\n // Check prerequisites\n const checkSpinner = spinner('Checking prerequisites...')\n const hasPython = await commandExists('python3')\n const hasNode = await commandExists('node')\n const hasNpm = await commandExists('npm')\n\n if (!hasPython) {\n checkSpinner.fail('Python 3 is required but not found. Install it from https://python.org')\n process.exit(1)\n }\n\n if (!hasNode || !hasNpm) {\n checkSpinner.fail('Node.js and npm are required but not found. Install from https://nodejs.org')\n process.exit(1)\n }\n\n checkSpinner.succeed('Prerequisites OK (Python 3, Node.js, npm)')\n\n const context = {\n projectName: name,\n backendPort,\n frontendPort,\n themePreset,\n }\n\n // 1. Create project directory and config\n fs.mkdirSync(projectDir, { recursive: true })\n fs.writeFileSync(\n path.join(projectDir, 'blacksmith.config.json'),\n JSON.stringify(\n {\n name,\n version: '0.1.0',\n backend: { port: backendPort },\n frontend: { port: frontendPort },\n },\n null,\n 2\n )\n )\n\n // 2. Generate backend\n const backendSpinner = spinner('Generating Django backend...')\n try {\n renderDirectory(\n path.join(templatesDir, 'backend'),\n backendDir,\n context\n )\n\n // Copy .env.example to .env for development\n fs.copyFileSync(\n path.join(backendDir, '.env.example'),\n path.join(backendDir, '.env')\n )\n\n backendSpinner.succeed('Django backend generated')\n } catch (error: any) {\n backendSpinner.fail('Failed to generate backend')\n log.error(error.message)\n process.exit(1)\n }\n\n // 3. Create Python virtual environment\n const venvSpinner = spinner('Creating Python virtual environment...')\n try {\n await exec('python3', ['-m', 'venv', 'venv'], { cwd: backendDir, silent: true })\n venvSpinner.succeed('Virtual environment created')\n } catch (error: any) {\n venvSpinner.fail('Failed to create virtual environment')\n log.error(error.message)\n process.exit(1)\n }\n\n // 4. Install Python dependencies\n const pipSpinner = spinner('Installing Python dependencies...')\n try {\n await execPip(\n ['install', '-r', 'requirements.txt'],\n backendDir,\n true\n )\n pipSpinner.succeed('Python dependencies installed')\n } catch (error: any) {\n pipSpinner.fail('Failed to install Python dependencies')\n log.error(error.message)\n process.exit(1)\n }\n\n // 5. Run Django migrations\n const migrateSpinner = spinner('Running initial migrations...')\n try {\n await execPython(['manage.py', 'makemigrations', 'users'], backendDir, true)\n await execPython(['manage.py', 'migrate'], backendDir, true)\n migrateSpinner.succeed('Database migrated')\n } catch (error: any) {\n migrateSpinner.fail('Failed to run migrations')\n log.error(error.message)\n process.exit(1)\n }\n\n // 6. Generate frontend\n const frontendSpinner = spinner('Generating React frontend...')\n try {\n renderDirectory(\n path.join(templatesDir, 'frontend'),\n frontendDir,\n context\n )\n frontendSpinner.succeed('React frontend generated')\n } catch (error: any) {\n frontendSpinner.fail('Failed to generate frontend')\n log.error(error.message)\n process.exit(1)\n }\n\n // 7. Install Node dependencies\n const npmSpinner = spinner('Installing Node.js dependencies...')\n try {\n await exec('npm', ['install'], { cwd: frontendDir, silent: true })\n npmSpinner.succeed('Node.js dependencies installed')\n } catch (error: any) {\n npmSpinner.fail('Failed to install Node.js dependencies')\n log.error(error.message)\n process.exit(1)\n }\n\n // 8. First OpenAPI sync (start Django temporarily)\n const syncSpinner = spinner('Running initial OpenAPI sync...')\n try {\n // Start Django in background\n const djangoProcess = spawn(\n './venv/bin/python',\n ['manage.py', 'runserver', `0.0.0.0:${backendPort}`, '--noreload'],\n {\n cwd: backendDir,\n stdio: 'ignore',\n detached: true,\n }\n )\n djangoProcess.unref()\n\n // Wait for Django to start\n await new Promise((resolve) => setTimeout(resolve, 4000))\n\n try {\n await exec(process.execPath, [path.join(frontendDir, 'node_modules', '.bin', 'openapi-ts')], { cwd: frontendDir, silent: true })\n syncSpinner.succeed('OpenAPI types synced')\n } catch {\n syncSpinner.warn('OpenAPI sync skipped (run \"blacksmith sync\" after starting Django)')\n }\n\n // Stop Django\n try {\n if (djangoProcess.pid) {\n process.kill(-djangoProcess.pid)\n }\n } catch {\n // Process may have already exited\n }\n } catch {\n syncSpinner.warn('OpenAPI sync skipped (run \"blacksmith sync\" after starting Django)')\n }\n\n // 9. Ensure generated API stub exists (openapi-ts may have cleared the directory)\n const generatedDir = path.join(frontendDir, 'src', 'api', 'generated')\n const stubFile = path.join(generatedDir, 'client.gen.ts')\n if (!fs.existsSync(stubFile)) {\n if (!fs.existsSync(generatedDir)) {\n fs.mkdirSync(generatedDir, { recursive: true })\n }\n fs.writeFileSync(\n stubFile,\n [\n '/**',\n ' * Auto-generated API Client',\n ' *',\n ' * This is a stub file that allows the app to boot before',\n ' * the first OpenAPI sync. Run `blacksmith sync` or `blacksmith dev`',\n ' * to generate the real client from your Django API schema.',\n ' *',\n ' * Generated by Blacksmith. This file will be overwritten by openapi-ts.',\n ' */',\n '',\n \"import { createClient } from '@hey-api/client-fetch'\",\n '',\n 'export const client = createClient()',\n '',\n ].join('\\n'),\n 'utf-8'\n )\n }\n\n // 10. AI development setup (opt-in)\n if (options.ai) {\n await setupAiDev({\n projectDir,\n projectName: name,\n includeBlacksmithUiSkill: options.blacksmithUiSkill !== false,\n })\n }\n\n // 11. Print success\n printNextSteps(name, backendPort, frontendPort)\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport Handlebars from 'handlebars'\n\n/**\n * Register custom Handlebars helpers\n */\nHandlebars.registerHelper('eq', (a: any, b: any) => a === b)\nHandlebars.registerHelper('ne', (a: any, b: any) => a !== b)\nHandlebars.registerHelper('upper', (str: string) => str?.toUpperCase())\nHandlebars.registerHelper('lower', (str: string) => str?.toLowerCase())\n\n/**\n * Render a Handlebars template string with context data.\n * Pre-processes JSX-style braces that collide with Handlebars triple-brace syntax.\n */\nexport function renderTemplate(templateStr: string, context: Record<string, any>): string {\n // Replace literal JSX braces adjacent to Handlebars expressions:\n // `{ {{var}}` → `OPEN_BRACE {{var}}` (prevents `{{{` triple-brace parse)\n // `{{var}}} ` → `{{var}} CLOSE_BRACE` (prevents `}}}` triple-brace parse)\n let safeStr = templateStr\n .replace(/\\{(\\s*)(?=\\{\\{[^{])/g, 'BLACKSMITH_OB$1')\n .replace(/([^}]\\}\\})(\\s*)\\}/g, '$1$2BLACKSMITH_CB')\n\n const template = Handlebars.compile(safeStr, { noEscape: true })\n const rendered = template(context)\n\n return rendered\n .replace(/BLACKSMITH_OB/g, '{')\n .replace(/BLACKSMITH_CB/g, '}')\n}\n\n/**\n * Read a template file and render it with context data\n */\nexport function renderTemplateFile(templatePath: string, context: Record<string, any>): string {\n const templateStr = fs.readFileSync(templatePath, 'utf-8')\n return renderTemplate(templateStr, context)\n}\n\n/**\n * Render a template file and write the output to a destination\n */\nexport function renderToFile(\n templatePath: string,\n destPath: string,\n context: Record<string, any>\n) {\n const rendered = renderTemplateFile(templatePath, context)\n const destDir = path.dirname(destPath)\n\n if (!fs.existsSync(destDir)) {\n fs.mkdirSync(destDir, { recursive: true })\n }\n\n fs.writeFileSync(destPath, rendered, 'utf-8')\n}\n\n/**\n * Recursively render all templates from a source directory to a destination directory.\n * Template files (.hbs) are rendered and written without the .hbs extension.\n * Non-template files are copied as-is.\n * Directory names and file names containing Handlebars expressions are also rendered.\n */\nexport function renderDirectory(\n srcDir: string,\n destDir: string,\n context: Record<string, any>\n) {\n if (!fs.existsSync(srcDir)) {\n throw new Error(`Template directory not found: ${srcDir}`)\n }\n\n const entries = fs.readdirSync(srcDir, { withFileTypes: true })\n\n for (const entry of entries) {\n // Render the name itself (for files like {{kebab}}-form.tsx.hbs)\n const renderedName = renderTemplate(entry.name, context)\n const srcPath = path.join(srcDir, entry.name)\n\n if (entry.isDirectory()) {\n const destSubDir = path.join(destDir, renderedName)\n renderDirectory(srcPath, destSubDir, context)\n } else if (entry.name.endsWith('.hbs')) {\n // Template file: render and write without .hbs extension\n const outputName = renderedName.replace(/\\.hbs$/, '')\n const destPath = path.join(destDir, outputName)\n renderToFile(srcPath, destPath, context)\n } else {\n // Non-template file: copy as-is\n const destPath = path.join(destDir, renderedName)\n const destDirPath = path.dirname(destPath)\n if (!fs.existsSync(destDirPath)) {\n fs.mkdirSync(destDirPath, { recursive: true })\n }\n fs.copyFileSync(srcPath, destPath)\n }\n }\n}\n\n/**\n * Append text to a file after a specific marker line\n */\nexport function appendAfterMarker(\n filePath: string,\n marker: string,\n content: string\n) {\n const fileContent = fs.readFileSync(filePath, 'utf-8')\n const lines = fileContent.split('\\n')\n const markerIndex = lines.findIndex((line) => line.includes(marker))\n\n if (markerIndex === -1) {\n throw new Error(`Marker \"${marker}\" not found in ${filePath}`)\n }\n\n lines.splice(markerIndex + 1, 0, content)\n fs.writeFileSync(filePath, lines.join('\\n'), 'utf-8')\n}\n\n/**\n * Insert text before a specific marker line\n */\nexport function insertBeforeMarker(\n filePath: string,\n marker: string,\n content: string\n) {\n const fileContent = fs.readFileSync(filePath, 'utf-8')\n const lines = fileContent.split('\\n')\n const markerIndex = lines.findIndex((line) => line.includes(marker))\n\n if (markerIndex === -1) {\n throw new Error(`Marker \"${marker}\" not found in ${filePath}`)\n }\n\n lines.splice(markerIndex, 0, content)\n fs.writeFileSync(filePath, lines.join('\\n'), 'utf-8')\n}\n","import { execa } from 'execa'\nimport { log } from './logger.js'\n\nexport interface ExecOptions {\n cwd?: string\n silent?: boolean\n env?: Record<string, string>\n}\n\n/**\n * Execute a shell command and return the result\n */\nexport async function exec(command: string, args: string[], options: ExecOptions = {}) {\n const { cwd, silent = false, env } = options\n\n try {\n const result = await execa(command, args, {\n cwd,\n env: { ...process.env, ...env },\n stdio: silent ? 'pipe' : 'inherit',\n })\n return result\n } catch (error: any) {\n if (!silent) {\n log.error(`Command failed: ${command} ${args.join(' ')}`)\n if (error.stderr) {\n log.error(error.stderr)\n }\n }\n throw error\n }\n}\n\n/**\n * Execute a shell command silently and return stdout\n */\nexport async function execSilent(command: string, args: string[], cwd?: string): Promise<string> {\n const result = await exec(command, args, { cwd, silent: true })\n return result.stdout\n}\n\n/**\n * Check if a command exists in PATH\n */\nexport async function commandExists(command: string): Promise<boolean> {\n try {\n await execa('which', [command], { stdio: 'pipe' })\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Run a Python command using the project's virtual environment\n */\nexport async function execPython(args: string[], cwd: string, silent = false) {\n const venvPython = `${cwd}/venv/bin/python`\n return exec(venvPython, args, { cwd, silent })\n}\n\n/**\n * Run pip using the project's virtual environment\n */\nexport async function execPip(args: string[], cwd: string, silent = false) {\n const venvPip = `${cwd}/venv/bin/pip`\n return exec(venvPip, args, { cwd, silent })\n}\n","import path from 'node:path'\nimport fs from 'node:fs'\nimport { fileURLToPath } from 'node:url'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = path.dirname(__filename)\n\n/**\n * Get the templates directory (relative to the built CLI)\n */\nexport function getTemplatesDir(): string {\n // In development: src/templates\n // In production (built): dist is sibling to src\n const devPath = path.resolve(__dirname, '..', 'templates')\n const prodPath = path.resolve(__dirname, '..', 'src', 'templates')\n\n if (fs.existsSync(devPath)) return devPath\n if (fs.existsSync(prodPath)) return prodPath\n\n throw new Error('Templates directory not found. Make sure the CLI is properly installed.')\n}\n\n/**\n * Find the Blacksmith project root by walking up directories\n * looking for blacksmith.config.json\n */\nexport function findProjectRoot(startDir?: string): string {\n let dir = startDir || process.cwd()\n\n while (dir !== path.dirname(dir)) {\n if (fs.existsSync(path.join(dir, 'blacksmith.config.json'))) {\n return dir\n }\n dir = path.dirname(dir)\n }\n\n throw new Error(\n 'Not inside a Blacksmith project. Run \"blacksmith init <name>\" to create one, or navigate to an existing Blacksmith project.'\n )\n}\n\n/**\n * Get the backend directory of a Blacksmith project\n */\nexport function getBackendDir(projectRoot?: string): string {\n const root = projectRoot || findProjectRoot()\n return path.join(root, 'backend')\n}\n\n/**\n * Get the frontend directory of a Blacksmith project\n */\nexport function getFrontendDir(projectRoot?: string): string {\n const root = projectRoot || findProjectRoot()\n return path.join(root, 'frontend')\n}\n\nexport interface BlacksmithConfig {\n name: string\n version: string\n backend: { port: number }\n frontend: { port: number }\n}\n\nexport function loadConfig(projectRoot?: string): BlacksmithConfig {\n const root = projectRoot || findProjectRoot()\n const configPath = path.join(root, 'blacksmith.config.json')\n return JSON.parse(fs.readFileSync(configPath, 'utf-8'))\n}\n\n/**\n * Check if a directory exists\n */\nexport function dirExists(dirPath: string): boolean {\n return fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()\n}\n\n/**\n * Check if a file exists\n */\nexport function fileExists(filePath: string): boolean {\n return fs.existsSync(filePath) && fs.statSync(filePath).isFile()\n}\n","import path from 'node:path'\nimport fs from 'node:fs'\nimport { log, spinner } from '../utils/logger.js'\nimport type { Skill, SkillContext } from '../skills/types.js'\nimport { coreRulesSkill } from '../skills/core-rules.js'\nimport { projectOverviewSkill } from '../skills/project-overview.js'\nimport { djangoSkill } from '../skills/django.js'\nimport { djangoRestAdvancedSkill } from '../skills/django-rest-advanced.js'\nimport { apiDocumentationSkill } from '../skills/api-documentation.js'\nimport { reactSkill } from '../skills/react.js'\nimport { reactQuerySkill } from '../skills/react-query.js'\nimport { pageStructureSkill } from '../skills/page-structure.js'\nimport { blacksmithUiReactSkill } from '../skills/blacksmith-ui-react.js'\nimport { blacksmithUiFormsSkill } from '../skills/blacksmith-ui-forms.js'\nimport { blacksmithUiAuthSkill } from '../skills/blacksmith-ui-auth.js'\nimport { blacksmithHooksSkill } from '../skills/blacksmith-hooks.js'\nimport { blacksmithCliSkill } from '../skills/blacksmith-cli.js'\nimport { uiDesignSkill } from '../skills/ui-design.js'\nimport { programmingParadigmsSkill } from '../skills/programming-paradigms.js'\nimport { cleanCodeSkill } from '../skills/clean-code.js'\nimport { aiGuidelinesSkill } from '../skills/ai-guidelines.js'\n\ninterface AiSetupOptions {\n projectDir: string\n projectName: string\n includeBlacksmithUiSkill: boolean\n}\n\nexport async function setupAiDev({ projectDir, projectName, includeBlacksmithUiSkill }: AiSetupOptions) {\n const aiSpinner = spinner('Setting up AI development environment...')\n\n try {\n const skills: Skill[] = [\n coreRulesSkill,\n projectOverviewSkill,\n djangoSkill,\n djangoRestAdvancedSkill,\n apiDocumentationSkill,\n reactSkill,\n reactQuerySkill,\n pageStructureSkill,\n ]\n\n if (includeBlacksmithUiSkill) {\n skills.push(blacksmithUiReactSkill)\n skills.push(blacksmithUiFormsSkill)\n skills.push(blacksmithUiAuthSkill)\n skills.push(blacksmithHooksSkill)\n skills.push(uiDesignSkill)\n }\n\n skills.push(blacksmithCliSkill)\n skills.push(programmingParadigmsSkill)\n skills.push(cleanCodeSkill)\n skills.push(aiGuidelinesSkill)\n\n const ctx: SkillContext = { projectName }\n\n // Separate inline skills (CLAUDE.md) from file-based skills (.claude/skills/[id]/SKILL.md)\n const inlineSkills = skills.filter((s) => !s.name)\n const fileSkills = skills.filter((s) => s.name)\n\n // Create .claude/skills/ directory (clean existing skill directories first)\n const skillsDir = path.join(projectDir, '.claude', 'skills')\n if (fs.existsSync(skillsDir)) {\n for (const entry of fs.readdirSync(skillsDir)) {\n const entryPath = path.join(skillsDir, entry)\n const stat = fs.statSync(entryPath)\n if (stat.isDirectory()) {\n fs.rmSync(entryPath, { recursive: true })\n } else if (entry.endsWith('.md')) {\n // Clean up legacy flat .md files\n fs.unlinkSync(entryPath)\n }\n }\n }\n fs.mkdirSync(skillsDir, { recursive: true })\n\n // Write each file-based skill to .claude/skills/[id]/SKILL.md with frontmatter\n for (const skill of fileSkills) {\n const skillDir = path.join(skillsDir, skill.id)\n fs.mkdirSync(skillDir, { recursive: true })\n const frontmatter = `---\\nname: ${skill.name}\\ndescription: ${skill.description}\\n---\\n\\n`\n const content = skill.render(ctx).trim()\n fs.writeFileSync(path.join(skillDir, 'SKILL.md'), frontmatter + content + '\\n', 'utf-8')\n }\n\n // Build CLAUDE.md with inline content + skills directory reference\n const inlineContent = inlineSkills.map((s) => s.render(ctx)).join('\\n')\n const skillsList = fileSkills.map((s) => `- \\`.claude/skills/${s.id}/SKILL.md\\` — ${s.name}`).join('\\n')\n\n const claudeMd = [\n inlineContent.trim(),\n '',\n '## AI Skills',\n '',\n 'Detailed skills and conventions are in `.claude/skills/`:',\n '',\n skillsList,\n '',\n 'These files are auto-loaded by Claude Code. Run `blacksmith setup:ai` to regenerate.',\n '',\n ].join('\\n')\n\n fs.writeFileSync(path.join(projectDir, 'CLAUDE.md'), claudeMd, 'utf-8')\n\n const skillNames = skills\n .filter((s) => s.id !== 'project-overview' && s.id !== 'ai-guidelines')\n .map((s) => s.id)\n .join(' + ')\n\n aiSpinner.succeed(`AI dev environment ready (${skillNames} skills)`)\n } catch (error: any) {\n aiSpinner.fail('Failed to set up AI development environment')\n log.error(error.message)\n }\n}\n","import type { Skill, SkillContext } from './types.js'\n\n/**\n * Core Rules — Inlined directly into CLAUDE.md (no `name` property).\n *\n * These are the most critical rules that must always be visible to the AI.\n * They are NOT a separate skill file — they appear at the top of CLAUDE.md.\n */\nexport const coreRulesSkill: Skill = {\n id: 'core-rules',\n // No `name` → content is inlined directly into CLAUDE.md, not a separate file\n\n render(_ctx: SkillContext): string {\n return `## Critical Rules\n\n> **These rules are mandatory. Violating them produces broken, inconsistent code.**\n\n### 1. Use \\`@blacksmith-ui/react\\` for ALL UI\n- **Layout**: Use \\`Stack\\`, \\`Flex\\`, \\`Grid\\`, \\`Box\\`, \\`Container\\` — NEVER \\`<div className=\"flex ...\">\\` or \\`<div className=\"grid ...\">\\`\n- **Typography**: Use \\`Typography\\` and \\`Text\\` — NEVER raw \\`<h1>\\`–\\`<h6>\\`, \\`<p>\\`, or \\`<span>\\` with text classes\n- **Separators**: Use \\`Divider\\` — NEVER \\`<hr>\\` or \\`<Separator>\\`\n- **Everything else**: \\`Button\\`, \\`Card\\`, \\`Badge\\`, \\`Input\\`, \\`Table\\`, \\`Dialog\\`, \\`Alert\\`, \\`Skeleton\\`, \\`EmptyState\\`, \\`StatCard\\`, etc.\n- See the \\`blacksmith-ui-react\\` skill for the full 60+ component list\n\n### 2. Pages Are Thin Orchestrators\n- A page file should be ~20-30 lines: import components, call hooks, compose JSX\n- Break every page into child components in a \\`components/\\` folder\n- See the \\`page-structure\\` skill for the full pattern with examples\n\n### 3. Components Render, Hooks Think\n- Extract ALL logic into hooks in a \\`hooks/\\` folder — API calls, mutations, form setup, filtering, pagination, debouncing, computed state\n- Components should contain only JSX composition, prop passing, and simple event handler wiring\n- The only \\`useState\\` acceptable inline in a component is a simple UI toggle (e.g. modal open/close)\n- If a component has more than one \\`useState\\`, one \\`useEffect\\`, or any \\`useApiQuery\\`/\\`useApiMutation\\` — extract to a hook\n\n### 4. Use the \\`Path\\` Enum — Never Hardcode Paths\n- All route paths are in \\`src/router/paths.ts\\` as a \\`Path\\` enum\n- Use \\`Path.Login\\`, \\`Path.Dashboard\\`, etc. in \\`navigate()\\`, \\`<Link to={}>\\`, and route definitions\n- When adding a new page, add its path to the enum before \\`// blacksmith:path\\`\n- Use \\`buildPath(Path.ResetPassword, { token })\\` for dynamic segments\n\n### 5. Follow the Page/Feature Folder Structure\n\\`\\`\\`\npages/<page>/\n├── <page>.tsx # Thin orchestrator (default export)\n├── routes.tsx # RouteObject[] using Path enum\n├── index.ts # Re-exports public API\n├── components/ # Child components\n└── hooks/ # Data hooks\n\\`\\`\\`\n- See the \\`page-structure\\` skill for full conventions\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const projectOverviewSkill: Skill = {\n id: 'project-overview',\n name: 'Project Overview',\n description: 'Overview of the project structure, commands, and development workflow.',\n\n render(ctx: SkillContext): string {\n return `# ${ctx.projectName}\n\nA fullstack web application built with **Django** (backend) and **React** (frontend), scaffolded by **Blacksmith CLI**.\n\n## Project Structure\n\n\\`\\`\\`\n${ctx.projectName}/\n├── backend/ # Django project\n│ ├── apps/ # Django apps (one per resource)\n│ │ └── users/ # Built-in user app\n│ ├── config/ # Django settings, urls, wsgi/asgi\n│ │ └── settings/ # Split settings (base, development, production)\n│ ├── manage.py\n│ ├── requirements.txt\n│ └── venv/ # Python virtual environment\n├── frontend/ # React + Vite project\n│ ├── src/\n│ │ ├── api/ # API client (auto-generated from OpenAPI)\n│ │ ├── features/ # Feature modules (auth, etc.)\n│ │ ├── pages/ # Top-level pages\n│ │ ├── router/ # React Router setup with guards\n│ │ ├── shared/ # Shared components and hooks\n│ │ └── styles/ # Global styles (Tailwind)\n│ ├── package.json\n│ └── tailwind.config.js\n├── blacksmith.config.json\n└── CLAUDE.md # This file\n\\`\\`\\`\n\n## Commands\n\n- \\`blacksmith dev\\` — Start Django + Vite + OpenAPI sync in parallel\n- \\`blacksmith sync\\` — Regenerate frontend API types from Django OpenAPI schema\n- \\`blacksmith make:resource <Name>\\` — Scaffold a full resource (model, serializer, viewset, hooks, pages)\n- \\`blacksmith build\\` — Production build (frontend + collectstatic)\n- \\`blacksmith eject\\` — Remove Blacksmith, keep a clean Django + React project\n\n## Development Workflow\n\n1. Define models in \\`backend/apps/<app>/models.py\\`\n2. Create serializers in \\`backend/apps/<app>/serializers.py\\`\n3. Add viewsets in \\`backend/apps/<app>/views.py\\` and register URLs in \\`backend/apps/<app>/urls.py\\`\n4. Run \\`blacksmith sync\\` to generate TypeScript types and API client\n5. Build frontend features using generated hooks in \\`frontend/src/features/\\`\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const djangoSkill: Skill = {\n id: 'django',\n name: 'Django Backend Conventions',\n description: 'Models, serializers, views, URLs, settings, migrations, and testing patterns for the Django backend.',\n\n render(_ctx: SkillContext): string {\n return `## Django Backend Conventions\n\n### Models\n- Models live in \\`backend/apps/<app>/models.py\\`\n- Use Django's ORM. Inherit from \\`models.Model\\`\n- Use \\`TimeStampedModel\\` pattern: add \\`created_at\\` and \\`updated_at\\` fields with \\`auto_now_add\\` and \\`auto_now\\`\n- Register models in \\`backend/apps/<app>/admin.py\\` for Django admin\n- Use descriptive \\`verbose_name\\` and \\`verbose_name_plural\\` in \\`Meta\\`\n- Define \\`__str__\\` on every model for readable admin and debugging output\n- Use \\`related_name\\` on all ForeignKey and ManyToManyField declarations\n- Prefer \\`TextField\\` over \\`CharField\\` when there is no strict max length requirement\n\n### Serializers\n- Use Django REST Framework serializers in \\`backend/apps/<app>/serializers.py\\`\n- Prefer \\`ModelSerializer\\` for standard CRUD operations\n- Use \\`serializers.Serializer\\` for custom input/output that does not map to a model\n- Add per-field validation via \\`validate_<field>(self, value)\\` methods\n- Add cross-field validation via \\`validate(self, attrs)\\`\n- Use \\`SerializerMethodField\\` for computed read-only fields\n- Nest related serializers for read endpoints; use PrimaryKeyRelatedField for write endpoints\n- Keep serializers thin — move business logic to model methods or service functions\n\n### Views\n- Use DRF \\`ModelViewSet\\` for standard CRUD endpoints\n- Use \\`@action(detail=True|False)\\` decorator for custom non-CRUD endpoints\n- Apply permissions with \\`permission_classes\\` at the class or action level\n- Use \\`@extend_schema\\` from \\`drf-spectacular\\` to document every endpoint — this powers the OpenAPI sync that generates frontend types\n- Use \\`filterset_fields\\`, \\`search_fields\\`, and \\`ordering_fields\\` for queryable list endpoints\n- Override \\`get_queryset()\\` to scope data to the current user when needed\n- Override \\`perform_create()\\` to inject \\`request.user\\` or other context into the serializer save\n\n### URLs\n- Each app has its own \\`urls.py\\` with a \\`DefaultRouter\\`\n- Register viewsets on the router: \\`router.register('resources', ResourceViewSet)\\`\n- App URLs are included in \\`backend/config/urls.py\\` under \\`/api/\\`\n- URL pattern: \\`/api/<resource>/\\` (list/create), \\`/api/<resource>/<id>/\\` (retrieve/update/delete)\n\n### Settings\n- Split settings: \\`base.py\\` (shared), \\`development.py\\` (local dev), \\`production.py\\` (deployment)\n- Environment variables loaded from \\`.env\\` via \\`django-environ\\`\n- Database: SQLite in development, configurable in production via \\`DATABASE_URL\\`\n- \\`INSTALLED_APPS\\` is declared in \\`base.py\\` — add new apps there\n- CORS, allowed hosts, and debug flags are environment-specific\n\n### Migrations\n- Run \\`./venv/bin/python manage.py makemigrations <app>\\` after model changes\n- Run \\`./venv/bin/python manage.py migrate\\` to apply\n- Never edit auto-generated migration files unless resolving a conflict\n- Use \\`RunPython\\` in data migrations for one-time data transformations\n\n### Testing\n- Tests live in \\`backend/apps/<app>/tests.py\\` (or a \\`tests/\\` package for larger apps)\n- Use \\`APITestCase\\` from DRF for API endpoint tests\n- Use \\`APIClient\\` with \\`force_authenticate(user)\\` for authenticated requests\n- Test both success and error paths (400, 401, 403, 404)\n- Run all tests: \\`cd backend && ./venv/bin/python manage.py test\\`\n- Run a single app: \\`cd backend && ./venv/bin/python manage.py test apps.<app>\\`\n\n### Adding a New App Manually\n1. Create the app directory under \\`backend/apps/\\` with \\`__init__.py\\`, \\`models.py\\`, \\`views.py\\`, \\`serializers.py\\`, \\`urls.py\\`, \\`admin.py\\`, \\`tests.py\\`\n2. Add \\`'apps.<app>'\\` to \\`INSTALLED_APPS\\` in \\`backend/config/settings/base.py\\`\n3. Include URLs in \\`backend/config/urls.py\\`: \\`path('api/<app>/', include('apps.<app>.urls'))\\`\n4. Run \\`makemigrations\\` and \\`migrate\\`\n5. Run \\`blacksmith sync\\` to update frontend types\n\n### Common Patterns\n- **Soft delete**: Add an \\`is_active\\` BooleanField and override \\`get_queryset()\\` to filter\n- **Pagination**: Configured globally in \\`REST_FRAMEWORK\\` settings — default is \\`PageNumberPagination\\`\n- **Permissions**: Use \\`IsAuthenticated\\` as default; create custom permissions in \\`permissions.py\\`\n- **Signals**: Use sparingly; prefer explicit calls in serializer/view logic\n- **Management commands**: Place in \\`backend/apps/<app>/management/commands/\\` for CLI tasks\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const djangoRestAdvancedSkill: Skill = {\n id: 'django-rest-advanced',\n name: 'Advanced Django REST Framework',\n description: 'Senior-level DRF patterns: service layer, query optimization, custom permissions, filters, caching, and testing.',\n\n render(_ctx: SkillContext): string {\n return `## Advanced Django REST Framework — Senior-Level Patterns\n\n> **RULE: Follow these patterns for production-grade, scalable, and maintainable DRF APIs.**\n> These build on top of the base Django conventions. Apply them when building non-trivial features.\n\n### Architecture: Service Layer Pattern\n\nKeep views and serializers thin. Extract business logic into service modules.\n\n\\`\\`\\`\nbackend/apps/<app>/\n├── models.py # Data + model-level methods only\n├── serializers.py # Validation + representation only\n├── views.py # HTTP glue + permissions only\n├── services.py # Business logic lives here\n├── selectors.py # Complex read queries\n├── permissions.py # Custom permission classes\n├── filters.py # Custom filter backends\n├── signals.py # Signal handlers (use sparingly)\n├── tasks.py # Celery/background tasks\n└── tests/\n ├── test_views.py\n ├── test_services.py\n └── test_selectors.py\n\\`\\`\\`\n\n\\`\\`\\`python\n# services.py — Business logic\nfrom django.db import transaction\n\nclass OrderService:\n @staticmethod\n @transaction.atomic\n def place_order(*, user, items, shipping_address):\n \"\"\"Place an order with inventory validation and payment.\"\"\"\n order = Order.objects.create(user=user, shipping_address=shipping_address)\n for item in items:\n if item['product'].stock < item['quantity']:\n raise ValidationError(f\"Insufficient stock for {item['product'].name}\")\n OrderItem.objects.create(order=order, **item)\n item['product'].stock -= item['quantity']\n item['product'].save(update_fields=['stock'])\n PaymentService.charge(user=user, amount=order.total)\n return order\n\\`\\`\\`\n\n\\`\\`\\`python\n# selectors.py — Complex read queries\nfrom django.db.models import Q, Count, Prefetch\n\nclass OrderSelector:\n @staticmethod\n def list_for_user(*, user, status=None, search=None):\n qs = (\n Order.objects\n .filter(user=user)\n .select_related('user', 'shipping_address')\n .prefetch_related(\n Prefetch('items', queryset=OrderItem.objects.select_related('product'))\n )\n .annotate(item_count=Count('items'))\n )\n if status:\n qs = qs.filter(status=status)\n if search:\n qs = qs.filter(Q(id__icontains=search) | Q(items__product__name__icontains=search))\n return qs.distinct().order_by('-created_at')\n\\`\\`\\`\n\n### Serializers: Advanced Patterns\n\n**Separate read and write serializers:**\n\\`\\`\\`python\nclass OrderListSerializer(serializers.ModelSerializer):\n \"\"\"Lightweight serializer for list endpoints.\"\"\"\n item_count = serializers.IntegerField(read_only=True)\n user = UserMinimalSerializer(read_only=True)\n\n class Meta:\n model = Order\n fields = ['id', 'status', 'total', 'item_count', 'user', 'created_at']\n\n\nclass OrderDetailSerializer(serializers.ModelSerializer):\n \"\"\"Full serializer for retrieve endpoints.\"\"\"\n items = OrderItemSerializer(many=True, read_only=True)\n user = UserSerializer(read_only=True)\n shipping_address = AddressSerializer(read_only=True)\n\n class Meta:\n model = Order\n fields = ['id', 'status', 'total', 'items', 'user', 'shipping_address', 'created_at', 'updated_at']\n\n\nclass OrderCreateSerializer(serializers.Serializer):\n \"\"\"Write serializer — validates input, delegates to service.\"\"\"\n items = OrderItemInputSerializer(many=True)\n shipping_address_id = serializers.PrimaryKeyRelatedField(queryset=Address.objects.all())\n\n def create(self, validated_data):\n return OrderService.place_order(\n user=self.context['request'].user,\n items=validated_data['items'],\n shipping_address=validated_data['shipping_address_id'],\n )\n\\`\\`\\`\n\n**Writable nested serializers:**\n\\`\\`\\`python\nclass ProjectSerializer(serializers.ModelSerializer):\n tags = TagSerializer(many=True, required=False)\n\n class Meta:\n model = Project\n fields = ['id', 'name', 'description', 'tags']\n\n def create(self, validated_data):\n tags_data = validated_data.pop('tags', [])\n project = Project.objects.create(**validated_data)\n for tag_data in tags_data:\n tag, _ = Tag.objects.get_or_create(**tag_data)\n project.tags.add(tag)\n return project\n\n def update(self, instance, validated_data):\n tags_data = validated_data.pop('tags', None)\n instance = super().update(instance, validated_data)\n if tags_data is not None:\n instance.tags.clear()\n for tag_data in tags_data:\n tag, _ = Tag.objects.get_or_create(**tag_data)\n instance.tags.add(tag)\n return instance\n\\`\\`\\`\n\n**Dynamic field serializers:**\n\\`\\`\\`python\nclass DynamicFieldsSerializer(serializers.ModelSerializer):\n \"\"\"Pass ?fields=id,name,email to limit response fields.\"\"\"\n def __init__(self, *args, **kwargs):\n fields = kwargs.pop('fields', None)\n super().__init__(*args, **kwargs)\n if fields is not None:\n allowed = set(fields)\n for field_name in set(self.fields) - allowed:\n self.fields.pop(field_name)\n\\`\\`\\`\n\n### ViewSets: Advanced Patterns\n\n**Use \\`get_serializer_class()\\` for action-specific serializers:**\n\\`\\`\\`python\nclass OrderViewSet(ModelViewSet):\n permission_classes = [IsAuthenticated]\n filterset_class = OrderFilterSet\n search_fields = ['items__product__name']\n ordering_fields = ['created_at', 'total']\n ordering = ['-created_at']\n\n def get_queryset(self):\n return OrderSelector.list_for_user(user=self.request.user)\n\n def get_serializer_class(self):\n if self.action == 'list':\n return OrderListSerializer\n if self.action == 'retrieve':\n return OrderDetailSerializer\n if self.action in ('create',):\n return OrderCreateSerializer\n return OrderUpdateSerializer\n\n def perform_create(self, serializer):\n serializer.save() # Service called inside serializer.create()\n\n @extend_schema(request=None, responses={200: OrderDetailSerializer})\n @action(detail=True, methods=['post'])\n def cancel(self, request, pk=None):\n order = self.get_object()\n OrderService.cancel_order(order=order, user=request.user)\n return Response(OrderDetailSerializer(order).data)\n\\`\\`\\`\n\n**Bulk operations:**\n\\`\\`\\`python\nclass BulkActionSerializer(serializers.Serializer):\n ids = serializers.ListField(child=serializers.IntegerField(), min_length=1, max_length=100)\n action = serializers.ChoiceField(choices=['archive', 'delete', 'export'])\n\n@extend_schema(request=BulkActionSerializer, responses={200: None})\n@action(detail=False, methods=['post'])\ndef bulk_action(self, request):\n serializer = BulkActionSerializer(data=request.data)\n serializer.is_valid(raise_exception=True)\n qs = self.get_queryset().filter(id__in=serializer.validated_data['ids'])\n action = serializer.validated_data['action']\n if action == 'archive':\n qs.update(status='archived')\n elif action == 'delete':\n qs.delete()\n return Response(status=status.HTTP_200_OK)\n\\`\\`\\`\n\n### QuerySet Optimization\n\n**ALWAYS optimize queries. N+1 queries are unacceptable.**\n\n\\`\\`\\`python\n# BAD — N+1 queries\norders = Order.objects.all()\nfor order in orders:\n print(order.user.email) # 1 query per order\n for item in order.items.all(): # 1 query per order\n print(item.product.name) # 1 query per item\n\n# GOOD — 3 queries total\norders = (\n Order.objects\n .select_related('user')\n .prefetch_related(\n Prefetch('items', queryset=OrderItem.objects.select_related('product'))\n )\n)\n\\`\\`\\`\n\n**Use \\`only()\\` / \\`defer()\\` for large tables:**\n\\`\\`\\`python\n# Only load fields you need for list views\nProduct.objects.only('id', 'name', 'price', 'thumbnail').filter(is_active=True)\n\\`\\`\\`\n\n**Use \\`Subquery\\` and \\`OuterRef\\` for correlated queries:**\n\\`\\`\\`python\nfrom django.db.models import Subquery, OuterRef\n\nlatest_comment = Comment.objects.filter(\n post=OuterRef('pk')\n).order_by('-created_at')\n\nposts = Post.objects.annotate(\n latest_comment_text=Subquery(latest_comment.values('text')[:1])\n)\n\\`\\`\\`\n\n### Custom Permissions\n\n\\`\\`\\`python\n# permissions.py\nfrom rest_framework.permissions import BasePermission\n\nclass IsOwner(BasePermission):\n \"\"\"Object-level permission: only the owner can modify.\"\"\"\n def has_object_permission(self, request, view, obj):\n return obj.user == request.user\n\n\nclass IsAdminOrReadOnly(BasePermission):\n def has_permission(self, request, view):\n if request.method in ('GET', 'HEAD', 'OPTIONS'):\n return True\n return request.user and request.user.is_staff\n\n\nclass HasRole(BasePermission):\n \"\"\"Usage: permission_classes = [HasRole('manager')]\"\"\"\n def __init__(self, role):\n self.role = role\n\n def has_permission(self, request, view):\n return hasattr(request.user, 'role') and request.user.role == self.role\n\\`\\`\\`\n\n**Combine permissions per action:**\n\\`\\`\\`python\nclass ProjectViewSet(ModelViewSet):\n def get_permissions(self):\n if self.action in ('update', 'partial_update', 'destroy'):\n return [IsAuthenticated(), IsOwner()]\n if self.action == 'create':\n return [IsAuthenticated()]\n return [AllowAny()]\n\\`\\`\\`\n\n### Custom Filters with django-filter\n\n\\`\\`\\`python\n# filters.py\nimport django_filters\nfrom .models import Order\n\nclass OrderFilterSet(django_filters.FilterSet):\n min_total = django_filters.NumberFilter(field_name='total', lookup_expr='gte')\n max_total = django_filters.NumberFilter(field_name='total', lookup_expr='lte')\n created_after = django_filters.DateFilter(field_name='created_at', lookup_expr='gte')\n created_before = django_filters.DateFilter(field_name='created_at', lookup_expr='lte')\n status = django_filters.MultipleChoiceFilter(choices=Order.STATUS_CHOICES)\n\n class Meta:\n model = Order\n fields = ['status', 'min_total', 'max_total', 'created_after', 'created_before']\n\\`\\`\\`\n\n### Pagination: Cursor-Based for Large Datasets\n\n\\`\\`\\`python\n# pagination.py\nfrom rest_framework.pagination import CursorPagination\n\nclass TimelinePagination(CursorPagination):\n page_size = 50\n ordering = '-created_at'\n cursor_query_param = 'cursor'\n\\`\\`\\`\n\nUse in viewset: \\`pagination_class = TimelinePagination\\`\n\n### Throttling\n\n\\`\\`\\`python\n# In settings\nREST_FRAMEWORK = {\n 'DEFAULT_THROTTLE_CLASSES': ['rest_framework.throttling.ScopedRateThrottle'],\n 'DEFAULT_THROTTLE_RATES': {\n 'auth': '5/min',\n 'uploads': '20/hour',\n 'burst': '60/min',\n },\n}\n\n# In view\nclass LoginView(APIView):\n throttle_scope = 'auth'\n\\`\\`\\`\n\n### Caching\n\n\\`\\`\\`python\nfrom django.views.decorators.cache import cache_page\nfrom django.utils.decorators import method_decorator\n\nclass ProductViewSet(ModelViewSet):\n @method_decorator(cache_page(60 * 15)) # 15 min cache\n def list(self, request, *args, **kwargs):\n return super().list(request, *args, **kwargs)\n\\`\\`\\`\n\n**Conditional caching with ETags:**\n\\`\\`\\`python\nfrom rest_framework_condition import condition\nfrom hashlib import md5\n\ndef product_etag(request, pk=None):\n product = Product.objects.only('updated_at').get(pk=pk)\n return md5(str(product.updated_at).encode()).hexdigest()\n\nclass ProductViewSet(ModelViewSet):\n @condition(etag_func=product_etag)\n def retrieve(self, request, *args, **kwargs):\n return super().retrieve(request, *args, **kwargs)\n\\`\\`\\`\n\n### Error Handling\n\n\\`\\`\\`python\n# exceptions.py\nfrom rest_framework.views import exception_handler\nfrom rest_framework.response import Response\n\ndef custom_exception_handler(exc, context):\n response = exception_handler(exc, context)\n if response is not None:\n response.data = {\n 'error': {\n 'code': response.status_code,\n 'message': response.data.get('detail', response.data),\n }\n }\n return response\n\\`\\`\\`\n\nRegister in settings: \\`'EXCEPTION_HANDLER': 'config.exceptions.custom_exception_handler'\\`\n\n### Versioning\n\n\\`\\`\\`python\n# settings\nREST_FRAMEWORK = {\n 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',\n 'ALLOWED_VERSIONS': ['v1', 'v2'],\n 'DEFAULT_VERSION': 'v1',\n}\n\n# urls.py\nurlpatterns = [\n path('api/<version>/', include('apps.core.urls')),\n]\n\n# views.py — Version-specific behavior\nclass UserViewSet(ModelViewSet):\n def get_serializer_class(self):\n if self.request.version == 'v2':\n return UserV2Serializer\n return UserV1Serializer\n\\`\\`\\`\n\n### Signals — Use Responsibly\n\n\\`\\`\\`python\n# signals.py — Only for cross-cutting concerns (audit logs, cache invalidation)\nfrom django.db.models.signals import post_save\nfrom django.dispatch import receiver\n\n@receiver(post_save, sender=Order)\ndef notify_on_order_placed(sender, instance, created, **kwargs):\n if created:\n NotificationService.send_order_confirmation(order=instance)\n\\`\\`\\`\n\nRegister in \\`apps.py\\`:\n\\`\\`\\`python\nclass OrdersConfig(AppConfig):\n def ready(self):\n import apps.orders.signals # noqa: F401\n\\`\\`\\`\n\n> **Prefer explicit service calls over signals for business logic.** Signals make flow hard to trace.\n\n### Testing: Senior-Level Patterns\n\n\\`\\`\\`python\nimport factory\nfrom rest_framework.test import APITestCase, APIClient\n\n# factories.py — Use factory_boy for test data\nclass UserFactory(factory.django.DjangoModelFactory):\n class Meta:\n model = User\n email = factory.Sequence(lambda n: f'user{n}@example.com')\n password = factory.PostGenerationMethodCall('set_password', 'testpass123')\n\n\nclass OrderFactory(factory.django.DjangoModelFactory):\n class Meta:\n model = Order\n user = factory.SubFactory(UserFactory)\n status = 'pending'\n\n\n# test_views.py\nclass OrderViewSetTest(APITestCase):\n def setUp(self):\n self.user = UserFactory()\n self.client = APIClient()\n self.client.force_authenticate(self.user)\n\n def test_list_returns_only_own_orders(self):\n OrderFactory.create_batch(3, user=self.user)\n OrderFactory.create_batch(2) # Other user's orders\n response = self.client.get('/api/orders/')\n self.assertEqual(response.status_code, 200)\n self.assertEqual(len(response.data['results']), 3)\n\n def test_create_validates_stock(self):\n product = ProductFactory(stock=0)\n response = self.client.post('/api/orders/', {\n 'items': [{'product_id': product.id, 'quantity': 1}],\n 'shipping_address_id': AddressFactory(user=self.user).id,\n }, format='json')\n self.assertEqual(response.status_code, 400)\n\n def test_cancel_forbidden_for_non_owner(self):\n order = OrderFactory() # Different user\n response = self.client.post(f'/api/orders/{order.id}/cancel/')\n self.assertEqual(response.status_code, 403)\n\\`\\`\\`\n\n**Test query count to prevent N+1 regressions:**\n\\`\\`\\`python\nfrom django.test.utils import override_settings\n\ndef test_list_query_count(self):\n OrderFactory.create_batch(10, user=self.user)\n with self.assertNumQueries(3): # 1 count + 1 orders + 1 prefetch items\n self.client.get('/api/orders/')\n\\`\\`\\`\n\n### API Documentation with drf-spectacular\n\n\\`\\`\\`python\nfrom drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiExample\n\n@extend_schema_view(\n list=extend_schema(\n summary=\"List orders\",\n parameters=[\n OpenApiParameter('status', str, description='Filter by status'),\n OpenApiParameter('search', str, description='Search by product name'),\n ],\n ),\n create=extend_schema(summary=\"Place a new order\"),\n cancel=extend_schema(summary=\"Cancel an order\", responses={200: OrderDetailSerializer}),\n)\nclass OrderViewSet(ModelViewSet):\n ...\n\\`\\`\\`\n\n### Key Principles\n\n1. **Fat services, thin views** — Views handle HTTP; services handle logic\n2. **Optimize every queryset** — Use \\`select_related\\`, \\`prefetch_related\\`, \\`only\\`, \\`annotate\\`\n3. **Separate read/write serializers** — List views are lightweight, detail views are rich, write views validate input\n4. **Test behavior, not implementation** — Test API contracts, permissions, and edge cases\n5. **Use \\`transaction.atomic\\`** — Wrap multi-step mutations to prevent partial writes\n6. **Document with \\`extend_schema\\`** — Every endpoint needs OpenAPI docs for frontend type generation\n7. **Scope querysets to user** — Never return data the user shouldn't see\n8. **Use cursor pagination for large datasets** — Offset pagination degrades at scale\n9. **Throttle sensitive endpoints** — Auth, uploads, and expensive operations\n10. **Version your API** — Plan for breaking changes from the start\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const apiDocumentationSkill: Skill = {\n id: 'api-documentation',\n name: 'API Documentation',\n description: 'drf-spectacular OpenAPI/Swagger documentation conventions for all API endpoints.',\n\n render(_ctx: SkillContext): string {\n return `## API Documentation — drf-spectacular (OpenAPI / Swagger)\n\n> **RULE: Every API endpoint MUST be documented with \\`@extend_schema\\` from \\`drf-spectacular\\`.**\n> Undocumented endpoints break the frontend type generation pipeline (\\`blacksmith sync\\`).\n> The OpenAPI schema powers auto-generated TypeScript types — accurate docs = accurate frontend types.\n\n### Setup\n\ndrf-spectacular is already configured in \\`backend/config/settings/base.py\\`:\n\n\\`\\`\\`python\nINSTALLED_APPS = [\n ...\n 'drf_spectacular',\n]\n\nREST_FRAMEWORK = {\n 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',\n}\n\nSPECTACULAR_SETTINGS = {\n 'TITLE': 'API',\n 'DESCRIPTION': 'API documentation',\n 'VERSION': '1.0.0',\n 'SERVE_INCLUDE_SCHEMA': False,\n}\n\\`\\`\\`\n\nDocs URLs in \\`backend/config/urls.py\\`:\n\\`\\`\\`python\nfrom drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView, SpectacularRedocView\n\nurlpatterns = [\n path('api/schema/', SpectacularAPIView.as_view(), name='schema'),\n path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),\n path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),\n]\n\\`\\`\\`\n\n### Decorating ViewSets — MANDATORY\n\n**Use \\`@extend_schema_view\\` on every ViewSet:**\n\\`\\`\\`python\nfrom drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiExample, OpenApiResponse\n\n@extend_schema_view(\n list=extend_schema(\n summary=\"List projects\",\n description=\"Returns paginated list of projects for the authenticated user.\",\n parameters=[\n OpenApiParameter('status', str, enum=['active', 'archived'], description='Filter by status'),\n OpenApiParameter('search', str, description='Search by name or description'),\n OpenApiParameter('ordering', str, description='Sort field (prefix with - for desc)', enum=['created_at', '-created_at', 'name', '-name']),\n ],\n responses={200: ProjectListSerializer},\n ),\n retrieve=extend_schema(\n summary=\"Get project details\",\n responses={200: ProjectDetailSerializer},\n ),\n create=extend_schema(\n summary=\"Create a project\",\n request=ProjectCreateSerializer,\n responses={201: ProjectDetailSerializer},\n examples=[\n OpenApiExample(\n 'Create project',\n value={'name': 'My Project', 'description': 'A new project'},\n request_only=True,\n ),\n ],\n ),\n update=extend_schema(\n summary=\"Update a project\",\n request=ProjectUpdateSerializer,\n responses={200: ProjectDetailSerializer},\n ),\n partial_update=extend_schema(\n summary=\"Partially update a project\",\n request=ProjectUpdateSerializer,\n responses={200: ProjectDetailSerializer},\n ),\n destroy=extend_schema(\n summary=\"Delete a project\",\n responses={204: None},\n ),\n)\nclass ProjectViewSet(ModelViewSet):\n ...\n\\`\\`\\`\n\n**Custom actions MUST also be decorated:**\n\\`\\`\\`python\n@extend_schema(\n summary=\"Archive a project\",\n request=None,\n responses={200: ProjectDetailSerializer},\n)\n@action(detail=True, methods=['post'])\ndef archive(self, request, pk=None):\n project = self.get_object()\n ProjectService.archive(project=project)\n return Response(ProjectDetailSerializer(project).data)\n\n\n@extend_schema(\n summary=\"Bulk delete projects\",\n request=BulkDeleteSerializer,\n responses={204: None},\n)\n@action(detail=False, methods=['post'])\ndef bulk_delete(self, request):\n ...\n\\`\\`\\`\n\n### Decorating APIViews\n\n\\`\\`\\`python\nclass DashboardStatsView(APIView):\n @extend_schema(\n summary=\"Get dashboard statistics\",\n responses={200: DashboardStatsSerializer},\n )\n def get(self, request):\n stats = DashboardSelector.get_stats(user=request.user)\n return Response(DashboardStatsSerializer(stats).data)\n\\`\\`\\`\n\n### Serializer Documentation\n\n**Use \\`help_text\\` on serializer fields — these become field descriptions in the schema:**\n\\`\\`\\`python\nclass ProjectCreateSerializer(serializers.Serializer):\n name = serializers.CharField(max_length=255, help_text=\"The project name. Must be unique per user.\")\n description = serializers.CharField(required=False, help_text=\"Optional project description.\")\n status = serializers.ChoiceField(\n choices=['active', 'archived'],\n default='active',\n help_text=\"Initial project status.\",\n )\n tags = serializers.ListField(\n child=serializers.CharField(),\n required=False,\n help_text=\"List of tag names to attach.\",\n )\n\\`\\`\\`\n\n**Use \\`@extend_schema_serializer\\` for serializer-level docs:**\n\\`\\`\\`python\nfrom drf_spectacular.utils import extend_schema_serializer, OpenApiExample\n\n@extend_schema_serializer(\n examples=[\n OpenApiExample(\n 'Project response',\n value={\n 'id': 1,\n 'name': 'My Project',\n 'status': 'active',\n 'created_at': '2025-01-15T10:30:00Z',\n },\n response_only=True,\n ),\n ]\n)\nclass ProjectDetailSerializer(serializers.ModelSerializer):\n class Meta:\n model = Project\n fields = ['id', 'name', 'description', 'status', 'created_at', 'updated_at']\n\\`\\`\\`\n\n### Response Types\n\n**Explicitly declare all possible response codes:**\n\\`\\`\\`python\n@extend_schema(\n summary=\"Place an order\",\n request=OrderCreateSerializer,\n responses={\n 201: OrderDetailSerializer,\n 400: OpenApiResponse(description=\"Validation error (insufficient stock, invalid address, etc.)\"),\n 401: OpenApiResponse(description=\"Authentication required\"),\n 403: OpenApiResponse(description=\"Insufficient permissions\"),\n },\n)\ndef create(self, request, *args, **kwargs):\n ...\n\\`\\`\\`\n\n### Enum and Choice Fields\n\n**Use \\`@extend_schema_field\\` for custom field types:**\n\\`\\`\\`python\nfrom drf_spectacular.utils import extend_schema_field\nfrom drf_spectacular.types import OpenApiTypes\n\n@extend_schema_field(OpenApiTypes.STR)\nclass ColorField(serializers.Field):\n ...\n\\`\\`\\`\n\n### Polymorphic / Union Responses\n\n\\`\\`\\`python\nfrom drf_spectacular.utils import PolymorphicProxySerializer\n\n@extend_schema(\n responses=PolymorphicProxySerializer(\n component_name='Notification',\n serializers={\n 'email': EmailNotificationSerializer,\n 'sms': SmsNotificationSerializer,\n 'push': PushNotificationSerializer,\n },\n resource_type_field_name='type',\n )\n)\ndef list(self, request):\n ...\n\\`\\`\\`\n\n### Pagination in Schema\n\ndrf-spectacular auto-wraps list responses with pagination. If using custom pagination:\n\\`\\`\\`python\nfrom drf_spectacular.utils import extend_schema\n\n@extend_schema(\n summary=\"List items\",\n responses=ItemSerializer(many=True), # Pagination wrapper is auto-applied\n)\ndef list(self, request, *args, **kwargs):\n ...\n\\`\\`\\`\n\n### Tags for Grouping\n\n**Group endpoints by feature using tags:**\n\\`\\`\\`python\n@extend_schema_view(\n list=extend_schema(tags=['Orders']),\n create=extend_schema(tags=['Orders']),\n retrieve=extend_schema(tags=['Orders']),\n)\nclass OrderViewSet(ModelViewSet):\n ...\n\\`\\`\\`\n\nOr set a default tag via \\`SPECTACULAR_SETTINGS\\`:\n\\`\\`\\`python\nSPECTACULAR_SETTINGS = {\n 'TAGS': [\n {'name': 'Auth', 'description': 'Authentication endpoints'},\n {'name': 'Orders', 'description': 'Order management'},\n {'name': 'Products', 'description': 'Product catalog'},\n ],\n}\n\\`\\`\\`\n\n### Authentication in Schema\n\n\\`\\`\\`python\nSPECTACULAR_SETTINGS = {\n 'SECURITY': [{'jwtAuth': []}],\n 'APPEND_COMPONENTS': {\n 'securitySchemes': {\n 'jwtAuth': {\n 'type': 'http',\n 'scheme': 'bearer',\n 'bearerFormat': 'JWT',\n }\n }\n },\n}\n\\`\\`\\`\n\n### Excluding Endpoints\n\n\\`\\`\\`python\n@extend_schema(exclude=True)\n@action(detail=False, methods=['get'])\ndef internal_health_check(self, request):\n ...\n\\`\\`\\`\n\n### Generating and Validating the Schema\n\n\\`\\`\\`bash\n# Generate schema file\n./venv/bin/python manage.py spectacular --file schema.yml\n\n# Validate schema for errors\n./venv/bin/python manage.py spectacular --validate\n\\`\\`\\`\n\n> **Always run \\`--validate\\` after adding new endpoints.** Fix any warnings before committing.\n\n### Rules\n\n1. **Every ViewSet** must have \\`@extend_schema_view\\` with summaries for all actions\n2. **Every custom \\`@action\\`** must have its own \\`@extend_schema\\` decorator\n3. **Every serializer field** that isn't self-explanatory must have \\`help_text\\`\n4. **Request and response serializers** must be explicitly declared — do not rely on auto-detection for non-trivial endpoints\n5. **All error responses** (400, 401, 403, 404) should be documented with \\`OpenApiResponse\\`\n6. **Run \\`manage.py spectacular --validate\\`** before committing to catch schema issues early\n7. **Use examples** (\\`OpenApiExample\\`) for complex request/response bodies\n8. **Group endpoints with tags** to keep Swagger UI organized\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const reactSkill: Skill = {\n id: 'react',\n name: 'React Frontend Conventions',\n description: 'Tech stack, project structure, state management, component patterns, styling, and testing for the React frontend.',\n\n render(_ctx: SkillContext): string {\n return `## React Frontend Conventions\n\n### Tech Stack\n- React 19 + TypeScript (strict mode)\n- Vite for bundling and dev server\n- TanStack React Query for server state management\n- React Router v7 for client-side routing\n- React Hook Form + Zod for forms and validation\n- Tailwind CSS for styling\n- \\`@hey-api/openapi-ts\\` for auto-generating API client from Django's OpenAPI schema\n- \\`lucide-react\\` for icons\n\n### API Layer\n- Auto-generated client in \\`frontend/src/api/generated/\\` — **never edit these files manually**\n- Custom API configuration (base URL, interceptors, auth headers) in \\`frontend/src/api/client.ts\\`\n- Query client setup and default options in \\`frontend/src/api/query-client.ts\\`\n- After any backend API change, run \\`blacksmith sync\\` to regenerate the client\n\n### Project Structure\n- See the \\`page-structure\\` skill for page folders, feature modules, routing, and route composition conventions\n- Shared, cross-feature code lives in \\`frontend/src/shared/\\`\n\n### State Management\n- **Server state**: TanStack React Query — see the \\`react-query\\` skill for full conventions on \\`useApiQuery\\` and \\`useApiMutation\\`\n- **Form state**: React Hook Form — manages form values, validation, submission\n- **Local UI state**: React \\`useState\\` / \\`useReducer\\` for component-scoped state\n- Avoid global state libraries unless there is a clear cross-cutting concern not covered by React Query\n\n### Component Patterns\n- Use functional components with named exports (not default exports for components)\n- Co-locate component, hook, and type in the same feature directory\n- Keep components focused — extract sub-components when a file exceeds ~150 lines\n- Use custom hooks to encapsulate data fetching and mutation logic\n- Prefer composition over prop drilling — use context for deeply shared state\n- **Pages must be thin orchestrators** — break into child components in \\`components/\\`, extract logic into \\`hooks/\\`. See the \\`page-structure\\` skill for the full pattern\n\n### UI Components\n- **All UI must use \\`@blacksmith-ui/react\\` components** — see the \\`blacksmith-ui-react\\` skill for the full component list\n- Use \\`Stack\\`, \\`Flex\\`, \\`Grid\\`, \\`Box\\` for layout — never raw \\`<div>\\` with flex/grid classes\n- Use \\`Typography\\` and \\`Text\\` for headings and text — never raw \\`<h1>\\`–\\`<h6>\\` or \\`<p>\\`\n- Use \\`Divider\\` instead of \\`<Separator>\\` or \\`<hr>\\`\n- Use \\`StatCard\\`, \\`EmptyState\\`, \\`Skeleton\\` instead of building custom equivalents\n\n### Route Paths\n- All route paths live in the \\`Path\\` enum at \\`src/router/paths.ts\\` — **never hardcode path strings**\n- Use \\`Path\\` in route definitions, \\`navigate()\\`, and \\`<Link to={}>\\`\n- Use \\`buildPath()\\` for dynamic segments — see the \\`page-structure\\` skill for details\n\n### Styling\n- Use Tailwind CSS utility classes for all styling\n- Use the \\`cn()\\` helper (from \\`clsx\\` + \\`tailwind-merge\\`) for conditional and merged classes\n- Theming via HSL CSS variables defined in \\`frontend/src/styles/globals.css\\`\n- Dark mode is supported via the \\`class\\` strategy on \\`<html>\\`\n- Use responsive prefixes (\\`sm:\\`, \\`md:\\`, \\`lg:\\`) for responsive layouts\n- Avoid inline \\`style\\` attributes — use Tailwind classes instead\n\n### Path Aliases\n- \\`@/\\` maps to \\`frontend/src/\\`\n- Always use the alias for imports: \\`import { useAuth } from '@/features/auth'\\`\n- Never use relative paths that go up more than one level (\\`../../\\`)\n\n### Error Handling\n- Use React Error Boundary (\\`frontend/src/router/error-boundary.tsx\\`) for render errors\n- API errors are handled by \\`useApiQuery\\` / \\`useApiMutation\\` — see the \\`react-query\\` skill for error display patterns\n- Display user-facing errors using the project's feedback components (Alert, Toast)\n\n### Testing\n- Run all tests: \\`cd frontend && npm test\\`\n- Run a specific test: \\`cd frontend && npm test -- --grep \"test name\"\\`\n- Test files live alongside the code they test (\\`component.test.tsx\\`)\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const reactQuerySkill: Skill = {\n id: 'react-query',\n name: 'TanStack React Query',\n description: 'API data fetching conventions using useApiQuery and useApiMutation wrappers.',\n\n render(_ctx: SkillContext): string {\n return `## TanStack React Query — API Data Fetching\n\n> **RULE: Always use \\`useApiQuery\\` and \\`useApiMutation\\` instead of raw \\`useQuery\\` / \\`useMutation\\`.**\n> These wrappers live in \\`@/shared/hooks/\\` and handle DRF error parsing, smart retry, and cache invalidation automatically.\n\n### Queries — \\`useApiQuery\\`\n\nImport: \\`import { useApiQuery } from '@/shared/hooks/use-api-query'\\`\n\nWraps \\`useQuery\\` with:\n- **Smart retry** — skips 400, 401, 403, 404, 405, 409, 422 (retries others up to 2 times)\n- **Parsed errors** — \\`errorMessage\\` (string) and \\`apiError\\` (structured) derived from \\`result.error\\`\n\n\\`\\`\\`tsx\n// Basic list query using generated options\nimport { postsListOptions } from '@/api/generated/@tanstack/react-query.gen'\n\nconst { data, isLoading, errorMessage } = useApiQuery({\n ...postsListOptions({ query: { page: 1 } }),\n})\n\n// With select to transform data\nconst { data, errorMessage } = useApiQuery({\n ...postsListOptions({ query: { page: 1 } }),\n select: (data: any) => ({\n posts: data.results ?? [],\n total: data.count ?? 0,\n }),\n})\n\n// Detail query\nimport { postsRetrieveOptions } from '@/api/generated/@tanstack/react-query.gen'\n\nconst { data: post, isLoading, errorMessage } = useApiQuery({\n ...postsRetrieveOptions({ path: { id: Number(id) } }),\n})\n\n// Conditional query (skip until id is available)\nconst { data } = useApiQuery({\n ...postsRetrieveOptions({ path: { id: Number(id) } }),\n enabled: !!id,\n})\n\\`\\`\\`\n\n**Return type extends \\`UseQueryResult\\` with:**\n| Field | Type | Description |\n|-------|------|-------------|\n| \\`errorMessage\\` | \\`string \\\\| null\\` | Human-readable error message |\n| \\`apiError\\` | \\`ApiError \\\\| null\\` | Structured error with \\`status\\`, \\`message\\`, \\`fieldErrors\\` |\n\n### Mutations — \\`useApiMutation\\`\n\nImport: \\`import { useApiMutation } from '@/shared/hooks/use-api-mutation'\\`\n\nWraps \\`useMutation\\` with:\n- **DRF error parsing** — \\`fieldErrors\\`, \\`errorMessage\\`, \\`apiError\\` derived from \\`mutation.error\\` (no local state)\n- **Cache invalidation** — pass \\`invalidateKeys\\` to auto-invalidate queries on success\n\n\\`\\`\\`tsx\n// Create mutation with cache invalidation\nimport {\n postsCreateMutation,\n postsListQueryKey,\n} from '@/api/generated/@tanstack/react-query.gen'\n\nconst createPost = useApiMutation({\n ...postsCreateMutation(),\n invalidateKeys: [postsListQueryKey()],\n})\n\n// Trigger the mutation\ncreatePost.mutate({ body: { title: 'Hello', content: '...' } })\n\n// Update mutation — invalidate both list and detail caches\nimport {\n postsUpdateMutation,\n postsRetrieveQueryKey,\n} from '@/api/generated/@tanstack/react-query.gen'\n\nconst updatePost = useApiMutation({\n ...postsUpdateMutation(),\n invalidateKeys: [\n postsListQueryKey(),\n postsRetrieveQueryKey({ path: { id } }),\n ],\n})\n\n// Delete with async/await\nconst deletePost = useApiMutation({\n ...postsDestroyMutation(),\n invalidateKeys: [postsListQueryKey()],\n})\n\nconst handleDelete = async () => {\n await deletePost.mutateAsync({ path: { id: Number(id) } })\n navigate('/posts')\n}\n\\`\\`\\`\n\n**Return type extends \\`UseMutationResult\\` with:**\n| Field | Type | Description |\n|-------|------|-------------|\n| \\`fieldErrors\\` | \\`Record<string, string[]>\\` | Per-field validation errors from DRF |\n| \\`errorMessage\\` | \\`string \\\\| null\\` | General error message |\n| \\`apiError\\` | \\`ApiError \\\\| null\\` | Full structured error |\n\n### Error Display Patterns\n\n\\`\\`\\`tsx\n// General error banner\n{mutation.errorMessage && (\n <Alert variant=\"destructive\">\n <AlertDescription>{mutation.errorMessage}</AlertDescription>\n </Alert>\n)}\n\n// Inline field errors in forms\nconst getFieldError = (field: string): string | undefined => {\n // Client-side (react-hook-form) errors take priority\n const clientError = form.formState.errors[field]?.message\n if (clientError) return clientError\n // Fall back to server-side field errors\n return mutation.fieldErrors[field]?.[0]\n}\n\n// Query error on a page\nconst { data, isLoading, errorMessage } = useApiQuery({ ... })\n\nif (errorMessage) {\n return (\n <Alert variant=\"destructive\">\n <AlertDescription>{errorMessage}</AlertDescription>\n </Alert>\n )\n}\n\\`\\`\\`\n\n### Creating Resource Hook Files\n\nWhen building hooks for a resource, create two files:\n\n**\\`use-<resources>.ts\\`** — List query hook:\n\\`\\`\\`tsx\nimport { useApiQuery } from '@/shared/hooks/use-api-query'\nimport { postsListOptions } from '@/api/generated/@tanstack/react-query.gen'\n\ninterface UsePostsParams {\n page?: number\n search?: string\n ordering?: string\n}\n\nexport function usePosts(params: UsePostsParams = {}) {\n return useApiQuery({\n ...postsListOptions({\n query: {\n page: params.page ?? 1,\n search: params.search,\n ordering: params.ordering ?? '-created_at',\n },\n }),\n select: (data: any) => ({\n posts: data.results ?? [],\n total: data.count ?? 0,\n hasNext: !!data.next,\n hasPrev: !!data.previous,\n }),\n })\n}\n\\`\\`\\`\n\n**\\`use-<resource>-mutations.ts\\`** — Create/update/delete hooks:\n\\`\\`\\`tsx\nimport { useApiMutation } from '@/shared/hooks/use-api-mutation'\nimport {\n postsCreateMutation,\n postsUpdateMutation,\n postsDestroyMutation,\n postsListQueryKey,\n postsRetrieveQueryKey,\n} from '@/api/generated/@tanstack/react-query.gen'\n\nexport function useCreatePost() {\n return useApiMutation({\n ...postsCreateMutation(),\n invalidateKeys: [postsListQueryKey()],\n })\n}\n\nexport function useUpdatePost(id: number) {\n return useApiMutation({\n ...postsUpdateMutation(),\n invalidateKeys: [\n postsListQueryKey(),\n postsRetrieveQueryKey({ path: { id } }),\n ],\n })\n}\n\nexport function useDeletePost() {\n return useApiMutation({\n ...postsDestroyMutation(),\n invalidateKeys: [postsListQueryKey()],\n })\n}\n\\`\\`\\`\n\n### Key Rules\n\n1. **Never use raw \\`useQuery\\` or \\`useMutation\\`** — always go through \\`useApiQuery\\` / \\`useApiMutation\\`\n2. **Never manage API error state with \\`useState\\`** — error state is derived from TanStack Query's \\`error\\` field\n3. **Always pass \\`invalidateKeys\\`** on mutations that modify data — ensures the UI stays in sync\n4. **Use generated options/mutations** from \\`@/api/generated/@tanstack/react-query.gen\\` — never write \\`queryFn\\` manually\n5. **Use \\`select\\`** to reshape API responses at the hook level, not in components\n6. **Use \\`enabled\\`** for conditional queries (e.g. waiting for an ID from URL params)\n7. **Spread generated options first** (\\`...postsListOptions()\\`), then add overrides — this preserves the generated \\`queryKey\\` and \\`queryFn\\`\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const pageStructureSkill: Skill = {\n id: 'page-structure',\n name: 'Page & Route Structure',\n description: 'Page folders, feature modules, routing conventions, and route composition patterns.',\n\n render(_ctx: SkillContext): string {\n return `## Page & Route Structure\n\n> **RULE: Every page and feature owns its own \\`routes.tsx\\`. The central router only composes them — never import page components directly into \\`routes.tsx\\`.**\n\n### Standalone Pages (\\`src/pages/\\`)\n\nEach page gets its own folder:\n\n\\`\\`\\`\npages/<page>/\n├── <page>.tsx # Page component (default export)\n├── routes.tsx # Exports RouteObject[] for this page\n├── index.ts # Re-exports public members (routes)\n├── components/ # Components private to this page (optional)\n└── hooks/ # Hooks private to this page (optional)\n\\`\\`\\`\n\n**\\`routes.tsx\\`** — defines the route config using the \\`Path\\` enum:\n\\`\\`\\`tsx\nimport type { RouteObject } from 'react-router-dom'\nimport { Path } from '@/router/paths'\nimport SettingsPage from './settings'\n\nexport const settingsRoutes: RouteObject[] = [\n { path: Path.Settings, element: <SettingsPage /> },\n]\n\\`\\`\\`\n\n**\\`index.ts\\`** — re-exports only public members:\n\\`\\`\\`ts\nexport { settingsRoutes } from './routes'\n\\`\\`\\`\n\n### Feature Pages (\\`src/features/\\`)\n\nFeatures that have pages include a \\`routes.tsx\\` at the feature root:\n\n\\`\\`\\`\nfeatures/<feature>/\n├── components/ # UI components scoped to this feature\n├── hooks/ # Custom hooks (queries, mutations, logic)\n├── pages/ # Page components (default exports)\n├── routes.tsx # RouteObject[] for all pages in this feature\n└── index.ts # Re-exports routes + public API\n\\`\\`\\`\n\n**\\`routes.tsx\\`** — groups related routes using the \\`Path\\` enum:\n\\`\\`\\`tsx\nimport { Outlet, type RouteObject } from 'react-router-dom'\nimport { Path } from '@/router/paths'\nimport PostsPage from './pages/posts-page'\nimport PostDetailPage from './pages/post-detail-page'\n\nexport const postsRoutes: RouteObject[] = [\n {\n path: Path.Posts,\n element: <Outlet />,\n children: [\n { index: true, element: <PostsPage /> },\n { path: ':id', element: <PostDetailPage /> },\n ],\n },\n]\n\\`\\`\\`\n\n**\\`index.ts\\`** — exports routes first:\n\\`\\`\\`ts\nexport { postsRoutes } from './routes'\nexport { usePosts } from './hooks/use-posts'\nexport { useCreatePost, useUpdatePost, useDeletePost } from './hooks/use-post-mutations'\n\\`\\`\\`\n\n### Route Paths (\\`src/router/paths.ts\\`)\n\nAll route paths are defined in a central \\`Path\\` enum — **never use hardcoded path strings**:\n\n\\`\\`\\`ts\nexport enum Path {\n Home = '/',\n Login = '/login',\n Register = '/register',\n ForgotPassword = '/forgot-password',\n ResetPassword = '/reset-password/:token',\n Dashboard = '/dashboard',\n // blacksmith:path\n}\n\\`\\`\\`\n\nUse \\`Path\\` everywhere — in route definitions, \\`navigate()\\`, \\`<Link to={}\\`\\`, etc.:\n\\`\\`\\`tsx\nimport { Path } from '@/router/paths'\n\n// In routes\n{ path: Path.Dashboard, element: <DashboardPage /> }\n\n// In navigation\nnavigate(Path.Login)\n<Link to={Path.Home}>Home</Link>\n\\`\\`\\`\n\nFor dynamic paths, use the \\`buildPath\\` helper:\n\\`\\`\\`ts\nimport { Path, buildPath } from '@/router/paths'\n\nbuildPath(Path.ResetPassword, { token: 'abc123' })\n// => '/reset-password/abc123'\n\\`\\`\\`\n\nThe \\`Path\\` enum is re-exported from \\`@/router\\` along with \\`buildPath\\`.\n\n### Central Router (\\`src/router/routes.tsx\\`)\n\nThe central router imports and spreads route arrays — it never imports page components directly:\n\n\\`\\`\\`tsx\nimport { homeRoutes } from '@/pages/home'\nimport { dashboardRoutes } from '@/pages/dashboard'\nimport { authRoutes } from '@/features/auth'\nimport { postsRoutes } from '@/features/posts'\n// blacksmith:import\n\nconst publicRoutes: RouteObject[] = [\n ...homeRoutes,\n]\n\nconst privateRoutes: RouteObject[] = [\n ...dashboardRoutes,\n ...postsRoutes,\n // blacksmith:routes\n]\n\\`\\`\\`\n\n### Auto-Registration\n\n\\`blacksmith make:resource\\` automatically registers routes using marker comments:\n- \\`// blacksmith:path\\` — new \\`Path\\` enum entry is inserted above this marker in \\`paths.ts\\`\n- \\`// blacksmith:import\\` — new import line is inserted above this marker in \\`routes.tsx\\`\n- \\`// blacksmith:routes\\` — new spread line is inserted above this marker in \\`routes.tsx\\`\n\nNever remove these markers. They must stay in the \\`Path\\` enum, \\`privateRoutes\\` array, and import block.\n\n### When to Use Pages vs Features\n\n| Use \\`pages/<page>/\\` | Use \\`features/<feature>/\\` |\n|---|---|\n| Standalone pages (home, dashboard, settings) | CRUD resources with multiple pages |\n| No shared hooks or components | Has hooks, components, and pages that belong together |\n| Single route | Multiple related routes (list + detail + edit) |\n\n### Component Decomposition\n\n> **RULE: Pages are orchestrators, not monoliths. Break every page into small, focused child components stored in \\`components/\\`.**\n\nA page component should read data (via hooks), pass it down as props, and compose child components. It should contain minimal JSX itself.\n\n\\`\\`\\`\npages/dashboard/\n├── dashboard.tsx # Page: composes children, calls hooks\n├── components/\n│ ├── stats-cards.tsx # Renders the stats grid\n│ ├── recent-activity.tsx # Renders activity feed\n│ └── quick-actions.tsx # Renders action buttons\n├── hooks/\n│ └── use-dashboard-data.ts # All data fetching for this page\n├── routes.tsx\n└── index.ts\n\\`\\`\\`\n\n\\`\\`\\`tsx\n// dashboard.tsx — thin orchestrator using @blacksmith-ui/react layout\nimport { Stack, Grid, Divider } from '@blacksmith-ui/react'\nimport { StatsCards } from './components/stats-cards'\nimport { RecentActivity } from './components/recent-activity'\nimport { QuickActions } from './components/quick-actions'\nimport { useDashboardData } from './hooks/use-dashboard-data'\n\nexport default function DashboardPage() {\n const { stats, activity, isLoading } = useDashboardData()\n\n return (\n <Stack gap={6}>\n <StatsCards stats={stats} isLoading={isLoading} />\n <Divider />\n <Grid columns={{ base: 1, lg: 3 }} gap={6}>\n <RecentActivity items={activity} isLoading={isLoading} className=\"lg:col-span-2\" />\n <QuickActions />\n </Grid>\n </Stack>\n )\n}\n\\`\\`\\`\n\n**When to extract a child component:**\n- A section of JSX exceeds ~30 lines\n- A block has its own loading/error state\n- A block is logically independent (e.g. a table, a form, a sidebar)\n- A block could be reused on another page (move to \\`shared/\\` or the feature's \\`components/\\`)\n\n**When NOT to extract:**\n- A few lines of simple, static markup (headings, wrappers)\n- Extracting would just move props through another layer with no clarity gain\n\n### Separating Logic into Hooks\n\n> **RULE: Components render. Hooks think. Never mix data fetching, transformations, or complex state logic into component bodies.**\n\nExtract logic into hooks in the \\`hooks/\\` folder co-located with the page or feature:\n\n\\`\\`\\`tsx\n// BAD — logic mixed into the component\nexport default function OrdersPage() {\n const [page, setPage] = useState(1)\n const [search, setSearch] = useState('')\n const debouncedSearch = useDebounce(search, 300)\n const { data, isLoading } = useApiQuery({\n ...ordersListOptions({ query: { page, search: debouncedSearch } }),\n select: (d: any) => ({ orders: d.results ?? [], total: d.count ?? 0 }),\n })\n\n const deleteOrder = useApiMutation({\n ...ordersDestroyMutation(),\n invalidateKeys: [ordersListQueryKey()],\n })\n\n return ( /* 200 lines of JSX using all of the above */ )\n}\n\\`\\`\\`\n\n\\`\\`\\`tsx\n// GOOD — logic in a hook, component just renders\n// hooks/use-orders-page.ts\nexport function useOrdersPage() {\n const [page, setPage] = useState(1)\n const [search, setSearch] = useState('')\n const debouncedSearch = useDebounce(search, 300)\n\n const { data, isLoading, errorMessage } = useOrders({\n page,\n search: debouncedSearch,\n })\n\n const deleteOrder = useDeleteOrder()\n\n return { orders: data?.orders ?? [], total: data?.total ?? 0, isLoading, errorMessage, page, setPage, search, setSearch, deleteOrder }\n}\n\n// orders-page.tsx\nimport { Stack } from '@blacksmith-ui/react'\nimport { useOrdersPage } from './hooks/use-orders-page'\nimport { OrdersTable } from './components/orders-table'\nimport { OrdersToolbar } from './components/orders-toolbar'\n\nexport default function OrdersPage() {\n const { orders, total, isLoading, page, setPage, search, setSearch, deleteOrder } = useOrdersPage()\n\n return (\n <Stack gap={4}>\n <OrdersToolbar search={search} onSearchChange={setSearch} />\n <OrdersTable orders={orders} isLoading={isLoading} onDelete={(id) => deleteOrder.mutate({ path: { id } })} />\n </Stack>\n )\n}\n\\`\\`\\`\n\n**What goes into a hook:**\n- API queries and mutations\n- Derived/computed state\n- Debouncing, pagination, filtering logic\n- Form setup (\\`useForm\\`, schema, submit handler)\n- Any \\`useEffect\\` or \\`useState\\` beyond a simple UI toggle\n\n**What stays in the component:**\n- Simple UI toggles (\\`useState\\` for a modal open/close is fine inline)\n- JSX composition and prop passing\n- Event handler wiring (calling \\`hook.mutate()\\`, \\`navigate()\\`, etc.)\n\n### Key Rules\n\n1. **Every page/feature owns its routes** — the route config lives next to the page, not in the central router\n2. **Use the \\`Path\\` enum for all route paths** — never hardcode path strings; import \\`Path\\` from \\`@/router/paths\\`\n3. **\\`index.ts\\` is the public API** — only export what other modules need (routes, hooks, components)\n4. **Page components use default exports** — this is the one exception to the named-export convention\n5. **Routes use named exports** — \\`export const settingsRoutes\\` not \\`export default\\`\n6. **Private components/hooks stay in the page folder** — if only one page uses it, co-locate it there\n7. **Never import across page folders** — if something is shared, move it to \\`shared/\\` or a feature\n8. **Keep marker comments intact** — \\`// blacksmith:path\\`, \\`// blacksmith:import\\`, and \\`// blacksmith:routes\\` are required for auto-registration\n9. **Pages are orchestrators** — break pages into child components in \\`components/\\`, extract logic into hooks in \\`hooks/\\`\n10. **Components render, hooks think** — never put data fetching, transformations, or complex state directly in component bodies\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const blacksmithUiReactSkill: Skill = {\n id: 'blacksmith-ui-react',\n name: '@blacksmith-ui/react',\n description: 'Core UI component library — 60+ components for layout, typography, inputs, data display, overlays, feedback, media, and navigation.',\n\n render(_ctx: SkillContext): string {\n return `## @blacksmith-ui/react — Core UI Components (60+)\n\n> **CRITICAL RULE: Every UI element MUST be built using \\`@blacksmith-ui/react\\` components — including layout and typography.**\n> Do NOT use raw HTML elements when a Blacksmith-UI component exists for that purpose.\n> This includes layout: use \\`Flex\\`, \\`Stack\\`, \\`Grid\\`, \\`Box\\`, \\`Container\\` instead of \\`<div>\\` with flex/grid classes.\n> This includes typography: use \\`Text\\` and \\`Typography\\` instead of raw \\`<h1>\\`–\\`<h6>\\`, \\`<p>\\`, \\`<span>\\`.\n\n### Layout\n\n| Component | Use instead of | Description |\n|-----------|---------------|-------------|\n| \\`Box\\` | \\`<div>\\` | Base layout primitive with style props |\n| \\`Flex\\` | \\`<div className=\"flex ...\">\\` | Flexbox container with style props (\\`direction\\`, \\`align\\`, \\`justify\\`, \\`gap\\`, \\`wrap\\`) |\n| \\`Grid\\` | \\`<div className=\"grid ...\">\\` | CSS Grid container (\\`columns\\`, \\`rows\\`, \\`gap\\`) |\n| \\`Stack\\` | \\`<div className=\"flex flex-col gap-...\">\\` | Vertical/horizontal stack (\\`direction\\`, \\`gap\\`) |\n| \\`Container\\` | \\`<div className=\"max-w-7xl mx-auto px-...\">\\` | Max-width centered container |\n| \\`Divider\\` | \\`<hr>\\` or border hacks | Visual separator (horizontal/vertical) |\n| \\`AspectRatio\\` | padding-bottom trick | Maintain aspect ratio for content |\n| \\`Resizable\\` | custom resize logic | Resizable panel groups |\n| \\`ScrollArea\\` | \\`overflow-auto\\` divs | Custom scrollbar container |\n\n### Typography\n\n| Component | Use instead of | Description |\n|-----------|---------------|-------------|\n| \\`Text\\` | \\`<p>\\`, \\`<span>\\` | Text display with style props (\\`size\\`, \\`weight\\`, \\`color\\`, \\`align\\`) |\n| \\`Typography\\` | \\`<h1>\\`–\\`<h6>\\`, \\`<p>\\` | Semantic heading/paragraph elements (\\`variant\\`: h1–h6, p, lead, muted, etc.) |\n| \\`Label\\` | \\`<label>\\` | Form label with accessibility support |\n\n### Cards & Containers\n\n- \\`Card\\`, \\`CardHeader\\`, \\`CardTitle\\`, \\`CardDescription\\`, \\`CardContent\\`, \\`CardFooter\\` — Use instead of styled \\`<div>\\` containers\n- \\`StatCard\\` — Use for metric/stat display (value, label, trend)\n- \\`EmptyState\\` — Use for empty content placeholders instead of custom empty divs\n\n### Actions\n\n- \\`Button\\` — Use instead of \\`<button>\\` or \\`<a>\\` styled as buttons\n - Variants: \\`default\\`, \\`secondary\\`, \\`destructive\\`, \\`outline\\`, \\`ghost\\`, \\`link\\`\n - Sizes: \\`sm\\`, \\`default\\`, \\`lg\\`, \\`icon\\`\n- \\`Toggle\\`, \\`ToggleGroup\\` — Use for toggle buttons\n- \\`DropdownMenu\\`, \\`DropdownMenuTrigger\\`, \\`DropdownMenuContent\\`, \\`DropdownMenuItem\\`, \\`DropdownMenuSeparator\\`, \\`DropdownMenuLabel\\` — Use for action menus\n- \\`ContextMenu\\` — Use for right-click menus\n- \\`Menubar\\` — Use for application menu bars\n- \\`AlertDialog\\`, \\`AlertDialogTrigger\\`, \\`AlertDialogContent\\`, \\`AlertDialogAction\\`, \\`AlertDialogCancel\\` — Use for destructive action confirmations\n\n### Data Entry\n\n- \\`Input\\` — Use instead of \\`<input>\\`\n- \\`SearchInput\\` — Use for search fields (has built-in search icon)\n- \\`Textarea\\` — Use instead of \\`<textarea>\\`\n- \\`NumberInput\\` — Use for numeric inputs with increment/decrement\n- \\`Select\\`, \\`SelectTrigger\\`, \\`SelectContent\\`, \\`SelectItem\\`, \\`SelectValue\\` — Use instead of \\`<select>\\`\n- \\`Checkbox\\` — Use instead of \\`<input type=\"checkbox\">\\`\n- \\`RadioGroup\\`, \\`RadioGroupItem\\` — Use instead of \\`<input type=\"radio\">\\`\n- \\`Switch\\` — Use for toggle switches\n- \\`Slider\\` — Use for single range inputs\n- \\`RangeSlider\\` — Use for dual-handle range selection\n- \\`DatePicker\\` — Use for date selection with calendar popup\n- \\`PinInput\\` / \\`InputOTP\\` — Use for PIN/OTP code entry\n- \\`ColorPicker\\` — Use for color selection\n- \\`FileUpload\\` — Use for file upload with drag & drop\n- \\`TagInput\\` — Use for tag/chip input with add/remove\n- \\`Rating\\` — Use for star/icon rating selection\n- \\`Label\\` — Use instead of \\`<label>\\`\n\n### Data Display\n\n- \\`Table\\`, \\`TableHeader\\`, \\`TableBody\\`, \\`TableRow\\`, \\`TableHead\\`, \\`TableCell\\` — Use instead of \\`<table>\\` elements\n- \\`DataTable\\` — Use for feature-rich tables with sorting, filtering, and pagination\n- \\`Badge\\` — Use for status indicators, tags, counts (variants: \\`default\\`, \\`secondary\\`, \\`destructive\\`, \\`outline\\`)\n- \\`Avatar\\`, \\`AvatarImage\\`, \\`AvatarFallback\\` — Use for user profile images\n- \\`Tooltip\\`, \\`TooltipTrigger\\`, \\`TooltipContent\\`, \\`TooltipProvider\\` — Use for hover hints\n- \\`HoverCard\\` — Use for rich hover content\n- \\`Calendar\\` — Use for full calendar display\n- \\`Chart\\` — Use for data visualization (powered by Recharts)\n- \\`Timeline\\` — Use for chronological event display\n- \\`Tree\\` — Use for hierarchical tree views\n- \\`List\\` — Use for structured list display instead of \\`<ul>\\`/\\`<ol>\\`\n- \\`Skeleton\\` — Use for loading placeholders\n- \\`Spinner\\` — Use for loading indicators\n- \\`Progress\\` — Use for progress bars\n- \\`Pagination\\`, \\`PaginationContent\\`, \\`PaginationItem\\`, \\`PaginationLink\\`, \\`PaginationNext\\`, \\`PaginationPrevious\\` — Use for paginated lists\n\n### Tabs & Accordion\n\n- \\`Tabs\\`, \\`TabsList\\`, \\`TabsTrigger\\`, \\`TabsContent\\` — Use for tabbed interfaces\n- \\`Accordion\\`, \\`AccordionItem\\`, \\`AccordionTrigger\\`, \\`AccordionContent\\` — Use for collapsible sections\n\n### Overlays\n\n- \\`Dialog\\`, \\`DialogTrigger\\`, \\`DialogContent\\`, \\`DialogHeader\\`, \\`DialogTitle\\`, \\`DialogDescription\\`, \\`DialogFooter\\` — Use for modals\n- \\`AlertDialog\\` — Use for confirmation dialogs\n- \\`Drawer\\` / \\`Sheet\\`, \\`SheetTrigger\\`, \\`SheetContent\\`, \\`SheetHeader\\`, \\`SheetTitle\\`, \\`SheetDescription\\` — Use for slide-out panels\n- \\`Popover\\` — Use for floating content panels\n- \\`CommandPalette\\` — Use for searchable command menus (cmdk-based)\n\n### Navigation\n\n- \\`Breadcrumb\\`, \\`BreadcrumbList\\`, \\`BreadcrumbItem\\`, \\`BreadcrumbLink\\`, \\`BreadcrumbSeparator\\` — Use for breadcrumb trails\n- \\`NavigationMenu\\`, \\`NavigationMenuList\\`, \\`NavigationMenuItem\\`, \\`NavigationMenuTrigger\\`, \\`NavigationMenuContent\\` — Use for site navigation\n- \\`Sidebar\\` — Use for app sidebars\n- \\`Dock\\` — Use for macOS-style dock navigation\n- \\`BackToTop\\` — Use for scroll-to-top buttons\n\n### Feedback\n\n- \\`Alert\\`, \\`AlertTitle\\`, \\`AlertDescription\\` — Use for inline messages/warnings\n- \\`AlertBanner\\` — Use for full-width alert banners\n- \\`Toast\\` / \\`Toaster\\` / \\`useToast\\` — Use for transient notifications\n- \\`SonnerToaster\\` — Sonner-based toast notifications\n\n### Media\n\n- \\`Image\\` — Use instead of \\`<img>\\` for optimized image display\n- \\`VideoPlayer\\` — Use for video playback\n- \\`CodeBlock\\` — Use for syntax-highlighted code (Shiki-powered)\n- \\`Carousel\\` — Use for image/content carousels\n- \\`Lightbox\\` — Use for full-screen media viewers\n\n### Specialized\n\n- \\`Stepper\\` / \\`Wizard\\` — Use for multi-step workflows\n- \\`NotificationCenter\\` / \\`useNotificationCenter\\` — Use for notification management\n- \\`SpotlightTour\\` — Use for guided feature tours\n\n### Utilities & Hooks\n\n- \\`cn()\\` — Merge class names (clsx + tailwind-merge)\n- \\`useToast()\\` — Programmatic toast notifications\n- \\`useMobile()\\` — Responsive breakpoint detection\n- \\`useDarkMode()\\` — Dark mode toggle. Returns \\`{ isDark, toggle }\\`\n\n---\n\n### Component-First Rules\n\n1. **Layout**: NEVER use \\`<div className=\"flex ...\">\\` or \\`<div className=\"grid ...\">\\`. Use \\`<Flex>\\`, \\`<Grid>\\`, \\`<Stack>\\`, \\`<Box>\\` from \\`@blacksmith-ui/react\\`.\n2. **Centering/max-width**: NEVER use \\`<div className=\"max-w-7xl mx-auto px-...\">\\`. Use \\`<Container>\\`.\n3. **Typography**: NEVER use raw \\`<h1>\\`–\\`<h6>\\` or \\`<p>\\` with Tailwind text classes. Use \\`<Typography variant=\"h2\">\\` or \\`<Text>\\`.\n4. **Separators**: NEVER use \\`<hr>\\` or border hacks. Use \\`<Divider>\\`.\n5. **Images**: NEVER use raw \\`<img>\\`. Use \\`<Image>\\` from \\`@blacksmith-ui/react\\` (use \\`Avatar\\` for profile pictures).\n6. **Lists**: NEVER use \\`<ul>\\`/\\`<ol>\\` for structured display lists. Use \\`<List>\\` from \\`@blacksmith-ui/react\\`. Plain \\`<ul>\\`/\\`<ol>\\` is only acceptable for simple inline content lists.\n7. **Buttons**: NEVER use \\`<button>\\` or \\`<a>\\` styled as a button. Use \\`<Button>\\`.\n8. **Inputs**: NEVER use \\`<input>\\`, \\`<textarea>\\`, \\`<select>\\` directly. Use the Blacksmith-UI equivalents.\n9. **Cards**: NEVER use a styled \\`<div>\\` as a card. Use \\`Card\\` + sub-components.\n10. **Tables**: NEVER use raw \\`<table>\\` HTML. Use \\`Table\\` or \\`DataTable\\`.\n11. **Loading**: NEVER use custom \\`animate-pulse\\` divs. Use \\`Skeleton\\` or \\`Spinner\\`.\n12. **Modals**: NEVER build custom modals. Use \\`Dialog\\`, \\`AlertDialog\\`, \\`Drawer\\`, or \\`Sheet\\`.\n13. **Feedback**: NEVER use plain styled text for errors/warnings. Use \\`Alert\\` or \\`useToast\\`.\n14. **Empty states**: NEVER build custom empty-state UIs. Use \\`EmptyState\\`.\n15. **Metrics**: NEVER build custom stat/metric cards. Use \\`StatCard\\`.\n\n### When Raw HTML IS Acceptable\n\n- \\`<main>\\`, \\`<section>\\`, \\`<header>\\`, \\`<footer>\\`, \\`<nav>\\`, \\`<article>\\`, \\`<aside>\\` — semantic HTML landmarks for page structure (but use \\`Flex\\`/\\`Stack\\`/\\`Grid\\` inside them for layout)\n- \\`<Link>\\` from react-router-dom — for page navigation (use \\`<Button asChild><Link>...</Link></Button>\\` if it needs button styling)\n- Icon components from \\`lucide-react\\`\n- \\`<form>\\` element when used with React Hook Form (but use \\`@blacksmith-ui/forms\\` components inside)\n\n### Design Tokens & Theming\n\n- \\`ThemeProvider\\` — Wrap app to apply preset or custom theme\n- Built-in presets: \\`default\\`, \\`blue\\`, \\`green\\`, \\`violet\\`, \\`red\\`, \\`neutral\\`\n- All components use HSL CSS variables (\\`--background\\`, \\`--foreground\\`, \\`--primary\\`, etc.)\n- Dark mode: \\`.dark\\` class strategy on \\`<html>\\`, or \\`<ThemeProvider mode=\"dark\">\\`\n- Border radius: controlled by \\`--radius\\` CSS variable\n- Extend with \\`className\\` prop + \\`cn()\\` utility for custom styles\n- Global styles: \\`@import '@blacksmith-ui/react/styles.css'\\` in app entry\n\n### Example: HowItWorks Section (Correct Way)\n\n\\`\\`\\`tsx\nimport { Container, Stack, Flex, Grid, Text, Typography, Image } from '@blacksmith-ui/react'\nimport { howItWorksSteps } from '../data'\n\nexport function HowItWorks() {\n return (\n <Box as=\"section\" className=\"py-16 sm:py-20\">\n <Container>\n <Stack gap={3} align=\"center\" className=\"mb-12\">\n <Typography variant=\"h2\">How It Works</Typography>\n <Text color=\"muted\">Book your stay in three simple steps</Text>\n </Stack>\n\n <Grid columns={{ base: 1, md: 3 }} gap={8} className=\"max-w-4xl mx-auto\">\n {howItWorksSteps.map((item) => (\n <Stack key={item.step} align=\"center\" gap={4}>\n <Box className=\"relative\">\n <Flex align=\"center\" justify=\"center\" className=\"h-16 w-16 rounded-full bg-primary text-primary-foreground shadow-lg shadow-primary/30\">\n <item.icon className=\"h-7 w-7\" />\n </Flex>\n <Flex align=\"center\" justify=\"center\" className=\"absolute -top-1 -right-1 h-6 w-6 rounded-full bg-background border-2 border-primary\">\n <Text size=\"xs\" weight=\"bold\" color=\"primary\">{item.step}</Text>\n </Flex>\n </Box>\n <Stack gap={2} align=\"center\">\n <Text size=\"lg\" weight=\"bold\">{item.title}</Text>\n <Text size=\"sm\" color=\"muted\" align=\"center\" className=\"max-w-xs\">\n {item.description}\n </Text>\n </Stack>\n </Stack>\n ))}\n </Grid>\n </Container>\n </Box>\n )\n}\n\\`\\`\\`\n\n### Example: Resource List Page (Correct Way)\n\n\\`\\`\\`tsx\nimport {\n Stack, Flex,\n Card, CardHeader, CardTitle, CardContent,\n Button, Badge, Skeleton,\n Table, TableHeader, TableBody, TableRow, TableHead, TableCell,\n DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem,\n AlertDialog, AlertDialogTrigger, AlertDialogContent,\n AlertDialogAction, AlertDialogCancel,\n} from '@blacksmith-ui/react'\nimport { MoreHorizontal, Plus, Trash2, Edit } from 'lucide-react'\nimport { Link } from 'react-router-dom'\n\nfunction ResourceListPage({ resources, isLoading, onDelete }) {\n if (isLoading) {\n return (\n <Card>\n <CardContent className=\"p-6\">\n <Stack gap={4}>\n {Array.from({ length: 5 }).map((_, i) => (\n <Skeleton key={i} className=\"h-12 w-full\" />\n ))}\n </Stack>\n </CardContent>\n </Card>\n )\n }\n\n return (\n <Card>\n <CardHeader>\n <Flex align=\"center\" justify=\"between\">\n <CardTitle>Resources</CardTitle>\n <Button asChild>\n <Link to=\"/resources/new\"><Plus className=\"mr-2 h-4 w-4\" /> Create</Link>\n </Button>\n </Flex>\n </CardHeader>\n <CardContent>\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Title</TableHead>\n <TableHead>Status</TableHead>\n <TableHead className=\"w-12\" />\n </TableRow>\n </TableHeader>\n <TableBody>\n {resources.map((r) => (\n <TableRow key={r.id}>\n <TableCell>{r.title}</TableCell>\n <TableCell><Badge variant=\"outline\">{r.status}</Badge></TableCell>\n <TableCell>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\">\n <MoreHorizontal className=\"h-4 w-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent>\n <DropdownMenuItem asChild>\n <Link to={\\`/resources/\\${r.id}/edit\\`}>\n <Edit className=\"mr-2 h-4 w-4\" /> Edit\n </Link>\n </DropdownMenuItem>\n <AlertDialog>\n <AlertDialogTrigger asChild>\n <DropdownMenuItem onSelect={(e) => e.preventDefault()}>\n <Trash2 className=\"mr-2 h-4 w-4\" /> Delete\n </DropdownMenuItem>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogAction onClick={() => onDelete(r.id)}>\n Delete\n </AlertDialogAction>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n </AlertDialogContent>\n </AlertDialog>\n </DropdownMenuContent>\n </DropdownMenu>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </CardContent>\n </Card>\n )\n}\n\\`\\`\\`\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const blacksmithUiFormsSkill: Skill = {\n id: 'blacksmith-ui-forms',\n name: '@blacksmith-ui/forms',\n description: 'Form components using React Hook Form + Zod for validation and submission.',\n\n render(_ctx: SkillContext): string {\n return `## @blacksmith-ui/forms — Form Components (React Hook Form + Zod)\n\n> **RULE: ALWAYS use these for forms.** Do NOT build forms with raw \\`<form>\\`, \\`<input>\\`, \\`<label>\\`, or manual error display.\n\n\\`\\`\\`tsx\nimport { Form, FormField, FormInput, FormTextarea, FormSelect } from '@blacksmith-ui/forms'\n\\`\\`\\`\n\n### Components\n\n- \\`Form\\` — Wraps the entire form. Props: \\`form\\` (useForm instance), \\`onSubmit\\`\n- \\`FormField\\` — Wraps each field. Props: \\`name\\`, \\`label\\`, \\`description?\\`\n- \\`FormInput\\` — Text input within FormField. Props: \\`type\\`, \\`placeholder\\`\n- \\`FormTextarea\\` — Textarea within FormField. Props: \\`rows\\`, \\`placeholder\\`\n- \\`FormSelect\\` — Select within FormField. Props: \\`options\\`, \\`placeholder\\`\n- \\`FormCheckbox\\` — Checkbox within FormField\n- \\`FormSwitch\\` — Toggle switch within FormField\n- \\`FormRadioGroup\\` — Radio group within FormField. Props: \\`options\\`\n- \\`FormDatePicker\\` — Date picker within FormField\n- \\`FormError\\` — Displays field-level validation error (auto-handled by FormField)\n- \\`FormDescription\\` — Displays helper text below a field\n\n### Rules\n- NEVER use raw \\`<form>\\` with manual \\`<label>\\` and error \\`<p>\\` tags. Always use \\`Form\\` + \\`FormField\\`.\n- NEVER use \\`<input>\\`, \\`<textarea>\\`, \\`<select>\\` inside forms. Use \\`FormInput\\`, \\`FormTextarea\\`, \\`FormSelect\\`.\n\n### Form Pattern — ALWAYS follow this:\n\\`\\`\\`tsx\nimport { Form, FormField, FormInput, FormTextarea, FormSelect } from '@blacksmith-ui/forms'\nimport { Button } from '@blacksmith-ui/react'\nimport { useForm } from 'react-hook-form'\nimport { zodResolver } from '@hookform/resolvers/zod'\nimport { z } from 'zod'\n\nconst schema = z.object({\n title: z.string().min(1, 'Title is required'),\n description: z.string().optional(),\n status: z.enum(['draft', 'published']),\n})\n\ntype FormData = z.infer<typeof schema>\n\nfunction ResourceForm({ defaultValues, onSubmit, isSubmitting }: Props) {\n const form = useForm<FormData>({\n resolver: zodResolver(schema),\n defaultValues: { title: '', description: '', status: 'draft', ...defaultValues },\n })\n\n return (\n <Form form={form} onSubmit={onSubmit}>\n <FormField name=\"title\" label=\"Title\">\n <FormInput placeholder=\"Enter title\" />\n </FormField>\n <FormField name=\"description\" label=\"Description\">\n <FormTextarea rows={4} placeholder=\"Enter description\" />\n </FormField>\n <FormField name=\"status\" label=\"Status\">\n <FormSelect options={[\n { label: 'Draft', value: 'draft' },\n { label: 'Published', value: 'published' },\n ]} />\n </FormField>\n <Button type=\"submit\" disabled={isSubmitting}>\n {isSubmitting ? 'Saving...' : 'Save'}\n </Button>\n </Form>\n )\n}\n\\`\\`\\`\n\n### Example: Detail Page with Edit Dialog\n\\`\\`\\`tsx\nimport {\n Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter,\n Button, Badge, Separator,\n Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogFooter,\n Alert, AlertTitle, AlertDescription,\n} from '@blacksmith-ui/react'\nimport { Form, FormField, FormInput, FormTextarea } from '@blacksmith-ui/forms'\nimport { useForm } from 'react-hook-form'\nimport { zodResolver } from '@hookform/resolvers/zod'\nimport { z } from 'zod'\nimport { Edit, ArrowLeft } from 'lucide-react'\nimport { Link } from 'react-router-dom'\n\nconst editSchema = z.object({\n title: z.string().min(1, 'Required'),\n description: z.string().optional(),\n})\n\nfunction ResourceDetailPage({ resource, onUpdate, error }) {\n const form = useForm({\n resolver: zodResolver(editSchema),\n defaultValues: { title: resource.title, description: resource.description },\n })\n\n return (\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <div>\n <CardTitle>{resource.title}</CardTitle>\n <CardDescription>Created {new Date(resource.created_at).toLocaleDateString()}</CardDescription>\n </div>\n <div className=\"flex gap-2\">\n <Button variant=\"outline\" asChild>\n <Link to=\"/resources\"><ArrowLeft className=\"mr-2 h-4 w-4\" /> Back</Link>\n </Button>\n <Dialog>\n <DialogTrigger asChild>\n <Button><Edit className=\"mr-2 h-4 w-4\" /> Edit</Button>\n </DialogTrigger>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>Edit Resource</DialogTitle>\n </DialogHeader>\n <Form form={form} onSubmit={onUpdate}>\n <FormField name=\"title\" label=\"Title\">\n <FormInput />\n </FormField>\n <FormField name=\"description\" label=\"Description\">\n <FormTextarea rows={4} />\n </FormField>\n <DialogFooter>\n <Button type=\"submit\">Save Changes</Button>\n </DialogFooter>\n </Form>\n </DialogContent>\n </Dialog>\n </div>\n </div>\n </CardHeader>\n <Separator />\n <CardContent className=\"pt-6\">\n {error && (\n <Alert variant=\"destructive\" className=\"mb-4\">\n <AlertTitle>Error</AlertTitle>\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n )}\n <p>{resource.description || 'No description provided.'}</p>\n </CardContent>\n <CardFooter>\n <Badge>{resource.status}</Badge>\n </CardFooter>\n </Card>\n )\n}\n\\`\\`\\`\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const blacksmithUiAuthSkill: Skill = {\n id: 'blacksmith-ui-auth',\n name: '@blacksmith-ui/auth',\n description: 'Authentication UI components and hooks for login, registration, and password reset.',\n\n render(_ctx: SkillContext): string {\n return `## @blacksmith-ui/auth — Authentication UI\n\n> **RULE: ALWAYS use these for auth pages.** Do NOT build custom login/register forms.\n\n\\`\\`\\`tsx\nimport { AuthProvider, LoginForm, RegisterForm, useAuth } from '@blacksmith-ui/auth'\n\\`\\`\\`\n\n### Components\n\n- \\`AuthProvider\\` — Context provider wrapping the app. Props: \\`config: { adapter, socialProviders? }\\`\n- \\`LoginForm\\` — Complete login form with email/password fields, validation, and links\n - Props: \\`onSubmit: (data: { email, password }) => void\\`, \\`onRegisterClick\\`, \\`onForgotPasswordClick\\`, \\`error\\`, \\`loading\\`\n- \\`RegisterForm\\` — Registration form with email, password, and display name\n - Props: \\`onSubmit: (data: { email, password, displayName }) => void\\`, \\`onLoginClick\\`, \\`error\\`, \\`loading\\`\n- \\`ForgotPasswordForm\\` — Password reset email request\n - Props: \\`onSubmit: (data: { email }) => void\\`, \\`onLoginClick\\`, \\`error\\`, \\`loading\\`\n- \\`ResetPasswordForm\\` — Set new password form\n - Props: \\`onSubmit: (data: { password, code }) => void\\`, \\`code\\`, \\`onLoginClick\\`, \\`error\\`, \\`loading\\`\n\n### Hooks\n\n- \\`useAuth\\` — Hook for auth state and actions\n - Returns: \\`user\\`, \\`loading\\`, \\`error\\`, \\`signInWithEmail(email, password)\\`, \\`signUpWithEmail(email, password, displayName?)\\`, \\`signOut()\\`, \\`sendPasswordResetEmail(email)\\`, \\`confirmPasswordReset(code, newPassword)\\`, \\`socialProviders\\`\n\n### Adapter\n\n- \\`AuthAdapter\\` — Interface for custom auth backends (Django JWT adapter already configured in \\`frontend/src/features/auth/adapter.ts\\`)\n\n### Rules\n- NEVER build custom login/register forms. Use \\`LoginForm\\`, \\`RegisterForm\\`, etc. from \\`@blacksmith-ui/auth\\`.\n- NEVER manage auth state manually. Use \\`useAuth\\` hook.\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const blacksmithHooksSkill: Skill = {\n id: 'blacksmith-hooks',\n name: '@blacksmith-ui/hooks',\n description: '74 production-ready React hooks for state, DOM, timers, async, browser APIs, and layout.',\n\n render(_ctx: SkillContext): string {\n return `## @blacksmith-ui/hooks — React Hooks Library\n\nA collection of 74 production-ready React hooks. SSR-safe, fully typed, zero dependencies, tree-shakeable.\n\n> **RULE: Use \\`@blacksmith-ui/hooks\\` instead of writing custom hooks when one exists for that purpose.**\n> Before creating a new hook, check if one already exists below.\n\n\\`\\`\\`tsx\nimport { useToggle, useLocalStorage, useDebounce, useClickOutside } from '@blacksmith-ui/hooks'\n\\`\\`\\`\n\n### State & Data\n\n| Hook | Description |\n|------|-------------|\n| \\`useToggle\\` | Boolean state with \\`toggle\\`, \\`on\\`, \\`off\\` actions |\n| \\`useDisclosure\\` | Open/close/toggle state for modals, drawers, etc. |\n| \\`useCounter\\` | Numeric counter with optional min/max clamping |\n| \\`useList\\` | Array state with push, remove, update, insert, filter, clear |\n| \\`useMap\\` | Map state with set, remove, clear helpers |\n| \\`useSet\\` | Set state with add, remove, toggle, has, clear helpers |\n| \\`useHistoryState\\` | State with undo/redo history |\n| \\`useDefault\\` | State that falls back to a default when set to null/undefined |\n| \\`useQueue\\` | FIFO queue data structure |\n| \\`useStack\\` | LIFO stack data structure |\n| \\`useLocalStorage\\` | Persist state to localStorage with JSON serialization |\n| \\`useSessionStorage\\` | Persist state to sessionStorage with JSON serialization |\n| \\`useUncontrolled\\` | Controlled/uncontrolled component pattern helper |\n\n### Values & Memoization\n\n| Hook | Description |\n|------|-------------|\n| \\`useDebounce\\` | Debounce a value with configurable delay |\n| \\`useDebouncedCallback\\` | Debounce a callback function |\n| \\`useThrottle\\` | Throttle a value with configurable interval |\n| \\`useThrottledCallback\\` | Throttle a callback function |\n| \\`usePrevious\\` | Track the previous value of a variable |\n| \\`useLatest\\` | Ref that always points to the latest value |\n| \\`useConst\\` | Compute a value once and return it on every render |\n| \\`useSyncedRef\\` | Keep a ref synchronized with the latest value |\n\n### DOM & Browser\n\n| Hook | Description |\n|------|-------------|\n| \\`useClickOutside\\` | Detect clicks outside a ref element |\n| \\`useEventListener\\` | Attach event listeners to window or elements |\n| \\`useElementSize\\` | Track element width/height via ResizeObserver |\n| \\`useHover\\` | Track mouse hover state |\n| \\`useKeyPress\\` | Listen for a specific key press |\n| \\`useKeyCombo\\` | Listen for key + modifier combinations |\n| \\`useLongPress\\` | Detect long press gestures |\n| \\`useFullscreen\\` | Manage the Fullscreen API |\n| \\`useTextSelection\\` | Track currently selected text |\n| \\`useFocusWithin\\` | Track whether focus is inside a container |\n| \\`useFocusTrap\\` | Trap Tab/Shift+Tab focus within a container |\n| \\`useBoundingClientRect\\` | Track element bounding rect via ResizeObserver |\n| \\`useSwipe\\` | Detect touch swipe direction |\n| \\`useDrag\\` | Track mouse drag with position and delta |\n| \\`useElementVisibility\\` | Check if an element is in the viewport |\n| \\`useScrollPosition\\` | Track window scroll position |\n| \\`useScrollLock\\` | Lock/unlock body scroll |\n| \\`useMutationObserver\\` | Observe DOM mutations |\n| \\`useIntersectionObserver\\` | Observe element intersection with viewport |\n\n### Timers & Lifecycle\n\n| Hook | Description |\n|------|-------------|\n| \\`useInterval\\` | setInterval wrapper with pause support |\n| \\`useTimeout\\` | setTimeout wrapper with manual clear |\n| \\`useCountdown\\` | Countdown timer with start/pause/reset |\n| \\`useStopwatch\\` | Stopwatch with lap support |\n| \\`useIdleTimer\\` | Detect user idle time |\n| \\`useUpdateEffect\\` | useEffect that skips the initial render |\n| \\`useIsomorphicLayoutEffect\\` | SSR-safe useLayoutEffect |\n| \\`useIsMounted\\` | Check if component is currently mounted |\n| \\`useIsFirstRender\\` | Check if this is the first render |\n\n### Async & Network\n\n| Hook | Description |\n|------|-------------|\n| \\`useFetch\\` | Declarative data fetching with loading/error states (use for external URLs; use TanStack Query for API calls) |\n| \\`useAsync\\` | Execute async functions with status tracking |\n| \\`useScript\\` | Dynamically load external scripts |\n| \\`useWebSocket\\` | WebSocket connection with auto-reconnect |\n| \\`useSSE\\` | Server-Sent Events (EventSource) wrapper |\n| \\`usePolling\\` | Poll an async function at a fixed interval |\n| \\`useAbortController\\` | Manage AbortController lifecycle |\n| \\`useRetry\\` | Retry async operations with exponential backoff |\n| \\`useSearch\\` | Filter arrays with debounced search |\n\n### Browser APIs\n\n| Hook | Description |\n|------|-------------|\n| \\`useMediaQuery\\` | Reactive CSS media query matching |\n| \\`useColorScheme\\` | Detect system color scheme preference |\n| \\`useCopyToClipboard\\` | Copy text to clipboard with status feedback |\n| \\`useOnline\\` | Track network connectivity |\n| \\`useWindowSize\\` | Track window dimensions |\n| \\`usePageVisibility\\` | Detect page visibility state |\n| \\`usePageLeave\\` | Detect when the user leaves the page |\n| \\`useFavicon\\` | Dynamically change the favicon |\n| \\`useReducedMotion\\` | Respect prefers-reduced-motion |\n| \\`useBreakpoint\\` | Responsive breakpoint detection |\n| \\`useIsClient\\` | SSR-safe client-side detection |\n\n### Layout & UI\n\n| Hook | Description |\n|------|-------------|\n| \\`useStickyHeader\\` | Detect when header should be sticky |\n| \\`useVirtualList\\` | Virtualized list rendering for large datasets |\n| \\`useInfiniteScroll\\` | Infinite scroll with threshold detection |\n| \\`useCollapse\\` | Collapse/expand animation with prop getters |\n| \\`useSteps\\` | Multi-step flow navigation |\n\n### Common Patterns\n\n**Modal with click-outside dismiss:**\n\\`\\`\\`tsx\nimport { useDisclosure, useClickOutside } from '@blacksmith-ui/hooks'\n\nfunction MyComponent() {\n const [opened, { open, close }] = useDisclosure(false)\n const ref = useClickOutside<HTMLDivElement>(close)\n\n return (\n <>\n <Button onClick={open}>Open</Button>\n {opened && <div ref={ref}>Modal content</div>}\n </>\n )\n}\n\\`\\`\\`\n\n**Debounced search:**\n\\`\\`\\`tsx\nimport { useDebounce, useSearch } from '@blacksmith-ui/hooks'\n\nfunction SearchPage({ items }) {\n const [query, setQuery] = useState('')\n const debouncedQuery = useDebounce(query, 300)\n const results = useSearch(items, debouncedQuery, ['title', 'description'])\n\n return (\n <>\n <Input value={query} onChange={(e) => setQuery(e.target.value)} />\n {results.map(item => <div key={item.id}>{item.title}</div>)}\n </>\n )\n}\n\\`\\`\\`\n\n**Persisted state with undo:**\n\\`\\`\\`tsx\nimport { useLocalStorage, useHistoryState } from '@blacksmith-ui/hooks'\n\nfunction Editor() {\n const [saved, setSaved] = useLocalStorage('draft', '')\n const [content, { set, undo, redo, canUndo, canRedo }] = useHistoryState(saved)\n\n const handleSave = () => setSaved(content)\n}\n\\`\\`\\`\n\n**Responsive layout:**\n\\`\\`\\`tsx\nimport { useBreakpoint, useWindowSize } from '@blacksmith-ui/hooks'\n\nfunction Layout({ children }) {\n const breakpoint = useBreakpoint({ sm: 640, md: 768, lg: 1024 })\n const isMobile = breakpoint === 'sm'\n\n return isMobile ? <MobileLayout>{children}</MobileLayout> : <DesktopLayout>{children}</DesktopLayout>\n}\n\\`\\`\\`\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const blacksmithCliSkill: Skill = {\n id: 'blacksmith-cli',\n name: 'Blacksmith CLI',\n description: 'CLI commands, configuration, and workflows for project scaffolding and management.',\n\n render(_ctx: SkillContext): string {\n return `## Blacksmith CLI\n\nBlacksmith is the CLI that scaffolded and manages this project. It lives outside the project directory as a globally installed npm package.\n\n### Commands Reference\n\n| Command | Description |\n|---|---|\n| \\`blacksmith init [name]\\` | Create a new project (interactive prompts if no flags) |\n| \\`blacksmith dev\\` | Start Django + Vite + OpenAPI watcher in parallel |\n| \\`blacksmith sync\\` | Regenerate frontend API client from Django OpenAPI schema |\n| \\`blacksmith make:resource <Name>\\` | Scaffold a full CRUD resource across backend and frontend |\n| \\`blacksmith build\\` | Production build (Vite build + Django collectstatic) |\n| \\`blacksmith eject\\` | Remove Blacksmith dependency, keep a clean project |\n| \\`blacksmith setup:ai\\` | Generate CLAUDE.md with AI development skills |\n| \\`blacksmith skills\\` | List all available AI development skills |\n\n### Configuration\n\nProject settings are stored in \\`blacksmith.config.json\\` at the project root:\n\n\\`\\`\\`json\n{\n \"name\": \"my-app\",\n \"version\": \"0.1.0\",\n \"backend\": { \"port\": 8000 },\n \"frontend\": { \"port\": 5173 }\n}\n\\`\\`\\`\n\n- **Ports** are read by \\`blacksmith dev\\` and \\`blacksmith sync\\` — change them here, not in code\n- The CLI finds the project root by walking up directories looking for this file\n\n### How \\`blacksmith dev\\` Works\n\nRuns three concurrent processes:\n1. **Django** — \\`./venv/bin/python manage.py runserver 0.0.0.0:<backend-port>\\`\n2. **Vite** — \\`npm run dev\\` in the frontend directory\n3. **OpenAPI watcher** — watches \\`.py\\` files in backend, runs \\`npx openapi-ts\\` on changes (2s debounce)\n\nAll three are managed by \\`concurrently\\` and stop together on Ctrl+C.\n\n### How \\`blacksmith make:resource\\` Works\n\nGiven a PascalCase name (e.g. \\`BlogPost\\`), it generates:\n\n**Backend:**\n- \\`backend/apps/blog_posts/models.py\\` — Django model with timestamps\n- \\`backend/apps/blog_posts/serializers.py\\` — DRF ModelSerializer\n- \\`backend/apps/blog_posts/views.py\\` — DRF ModelViewSet with drf-spectacular schemas\n- \\`backend/apps/blog_posts/urls.py\\` — DefaultRouter registration\n- \\`backend/apps/blog_posts/admin.py\\` — Admin registration\n- Wires the app into \\`INSTALLED_APPS\\` and \\`config/urls.py\\`\n- Runs \\`makemigrations\\` and \\`migrate\\`\n\n**Frontend:**\n- \\`frontend/src/features/blog-posts/\\` — Feature module with hooks and components\n- \\`frontend/src/pages/blog-posts/\\` — List and detail pages\n- Registers route path in \\`frontend/src/router/paths.ts\\` (\\`Path\\` enum)\n- Registers routes in \\`frontend/src/router/routes.tsx\\`\n\nThen runs \\`blacksmith sync\\` to generate the TypeScript API client.\n\n### How \\`blacksmith sync\\` Works\n\n1. Fetches the OpenAPI schema from \\`http://localhost:<backend-port>/api/schema/\\`\n2. Runs \\`openapi-ts\\` to generate TypeScript types, Zod schemas, SDK functions, and TanStack Query hooks\n3. Output goes to \\`frontend/src/api/generated/\\` — never edit these files manually\n\n### Init Flags\n\n\\`blacksmith init\\` supports both interactive prompts and CLI flags:\n\n\\`\\`\\`bash\n# Fully interactive\nblacksmith init\n\n# Skip prompts with flags\nblacksmith init my-app -b 9000 -f 3000 --ai\n\\`\\`\\`\n\n| Flag | Description |\n|---|---|\n| \\`-b, --backend-port <port>\\` | Django port (default: 8000) |\n| \\`-f, --frontend-port <port>\\` | Vite port (default: 5173) |\n| \\`--ai\\` | Generate CLAUDE.md with project skills |\n| \\`--no-blacksmith-ui-skill\\` | Exclude blacksmith-ui skill from CLAUDE.md |\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const uiDesignSkill: Skill = {\n id: 'ui-design',\n name: 'UI/UX Design System',\n description: 'Modern flat design principles, spacing, typography, color, layout patterns, and interaction guidelines aligned with the BlacksmithUI design language.',\n\n render(_ctx: SkillContext): string {\n return `## UI/UX Design System — Modern Flat Design\n\n> **Design philosophy: Clean, flat, content-first.**\n> BlacksmithUI follows the same design language as Anthropic, Apple, Linear, Vercel, and OpenAI — minimal chrome, generous whitespace, subtle depth, and purposeful motion. Every UI you build must conform to this standard.\n\n### Core Principles\n\n1. **Flat over skeuomorphic** — No gradients on surfaces, no heavy drop shadows, no bevels. Use solid colors, subtle borders, and minimal \\`shadow-sm\\` / \\`shadow-md\\` only where elevation is meaningful (cards, dropdowns, modals).\n2. **Content over decoration** — UI exists to present content, not to look busy. Remove any element that doesn't serve the user. If a section looks empty, the content is the problem — not the lack of decorative elements.\n3. **Whitespace is a feature** — Generous padding and margins create hierarchy and breathing room. Cramped UIs feel cheap. When in doubt, add more space.\n4. **Consistency over creativity** — Every page should feel like part of the same app. Use the same spacing scale, the same component patterns, the same interaction behaviors everywhere.\n5. **Progressive disclosure** — Show only what's needed at each level. Use expandable sections, tabs, dialogs, and drill-down navigation to manage complexity. Don't overwhelm with everything at once.\n\n### Spacing System\n\nUse Tailwind's spacing scale consistently. Do NOT use arbitrary values (\\`p-[13px]\\`) — stick to the system.\n\n| Scale | Value | Use for |\n|-------|-------|---------|\n| \\`1\\`–\\`2\\` | 4–8px | Inline gaps, icon-to-text spacing, tight badge padding |\n| \\`3\\`–\\`4\\` | 12–16px | Inner component padding, gap between related items |\n| \\`5\\`–\\`6\\` | 20–24px | Card padding, section inner spacing |\n| \\`8\\` | 32px | Gap between sections within a page |\n| \\`10\\`–\\`12\\` | 40–48px | Gap between major page sections |\n| \\`16\\`–\\`20\\` | 64–80px | Page-level vertical padding (hero, landing sections) |\n\n**Rules:**\n- Use \\`gap\\` (via \\`Flex\\`, \\`Stack\\`, \\`Grid\\`) for spacing between siblings — not margin on individual items\n- Use \\`Stack gap={...}\\` for vertical rhythm within a section\n- Page content padding: \\`px-4 sm:px-6 lg:px-8\\` (use \\`Container\\` which handles this)\n- Card body padding: \\`p-6\\` standard, \\`p-4\\` for compact cards\n- Never mix spacing approaches in the same context — pick gap OR margin, not both\n\n### Typography\n\nUse \\`Typography\\` and \\`Text\\` components from \\`@blacksmith-ui/react\\`. Do NOT style raw HTML headings.\n\n**Hierarchy:**\n| Level | Component | Use for |\n|-------|-----------|---------|\n| Page title | \\`<Typography variant=\"h1\">\\` | One per page. The main heading. |\n| Section title | \\`<Typography variant=\"h2\">\\` | Major sections within a page |\n| Sub-section | \\`<Typography variant=\"h3\">\\` | Groups within a section |\n| Card title | \\`<Typography variant=\"h4\">\\` or \\`CardTitle\\` | Card headings |\n| Body | \\`<Text>\\` | Paragraphs, descriptions |\n| Caption/label | \\`<Text size=\"sm\" color=\"muted\">\\` | Secondary info, metadata, timestamps |\n| Overline | \\`<Text size=\"xs\" weight=\"medium\" className=\"uppercase tracking-wide\">\\` | Category labels, section overlines |\n\n**Rules:**\n- One \\`h1\\` per page — it's the page title\n- Headings should never skip levels (h1 → h3 without h2)\n- Body text: \\`text-sm\\` (14px) for dense UIs (tables, sidebars), \\`text-base\\` (16px) for reading content\n- Line height: use Tailwind defaults (\\`leading-relaxed\\` for body copy, \\`leading-tight\\` for headings)\n- Max reading width: \\`max-w-prose\\` (~65ch) for long-form text. Never let paragraphs stretch full-width\n- Use \\`text-muted-foreground\\` for secondary text, never gray hardcoded values\n- Font weight: \\`font-medium\\` (500) for labels and emphasis, \\`font-semibold\\` (600) for headings, \\`font-bold\\` (700) sparingly\n\n### Color\n\nUse design tokens (CSS variables), never hardcoded colors.\n\n**Semantic palette:**\n| Token | Usage |\n|-------|-------|\n| \\`primary\\` | Primary actions (buttons, links, active states) |\n| \\`secondary\\` | Secondary actions, subtle backgrounds |\n| \\`destructive\\` | Delete, error, danger states |\n| \\`muted\\` | Backgrounds for subtle sections, disabled states |\n| \\`accent\\` | Highlights, hover states, focus rings |\n| \\`foreground\\` | Primary text |\n| \\`muted-foreground\\` | Secondary/helper text |\n| \\`border\\` | Borders, dividers |\n| \\`card\\` | Card backgrounds |\n| \\`background\\` | Page background |\n\n**Rules:**\n- NEVER use Tailwind color literals (\\`text-gray-500\\`, \\`bg-blue-600\\`, \\`border-slate-200\\`, \\`bg-white\\`, \\`bg-black\\`). Always use semantic tokens (\\`text-muted-foreground\\`, \\`bg-primary\\`, \\`border-border\\`, \\`bg-background\\`). This is non-negotiable — hardcoded colors break dark mode.\n- Status colors: use \\`Badge\\` variants (\\`default\\`, \\`secondary\\`, \\`destructive\\`, \\`outline\\`) — don't hand-roll colored pills.\n- Maximum 2–3 colors visible at any time (primary + foreground + muted). Colorful UIs feel noisy.\n- Every UI must render correctly in both light and dark mode. See the Dark Mode section below for the full rules.\n\n### Layout Patterns\n\n**Page layout:**\n\\`\\`\\`tsx\n<Box as=\"main\">\n <Container>\n <Stack gap={8}>\n {/* Page header */}\n <Flex align=\"center\" justify=\"between\">\n <Stack gap={1}>\n <Typography variant=\"h1\">Page Title</Typography>\n <Text color=\"muted\">Brief description of this page</Text>\n </Stack>\n <Button>Primary Action</Button>\n </Flex>\n\n {/* Page content sections */}\n <Stack gap={6}>\n {/* ... */}\n </Stack>\n </Stack>\n </Container>\n</Box>\n\\`\\`\\`\n\n**Card-based content:**\n\\`\\`\\`tsx\n<Grid columns={{ base: 1, md: 2, lg: 3 }} gap={6}>\n {items.map((item) => (\n <Card key={item.id}>\n <CardHeader>\n <CardTitle>{item.title}</CardTitle>\n <CardDescription>{item.description}</CardDescription>\n </CardHeader>\n <CardContent>\n {/* Content */}\n </CardContent>\n </Card>\n ))}\n</Grid>\n\\`\\`\\`\n\n**Sidebar + main content:**\n\\`\\`\\`tsx\n<Flex className=\"min-h-screen\">\n <Sidebar>{/* Nav items */}</Sidebar>\n <Box as=\"main\" className=\"flex-1\">\n <Container>{/* Page content */}</Container>\n </Box>\n</Flex>\n\\`\\`\\`\n\n**Section with centered content (landing pages):**\n\\`\\`\\`tsx\n<Box as=\"section\" className=\"py-16 sm:py-20\">\n <Container>\n <Stack gap={4} align=\"center\" className=\"text-center\">\n <Typography variant=\"h2\">Section Title</Typography>\n <Text color=\"muted\" className=\"max-w-2xl\">\n A concise description that explains the value proposition.\n </Text>\n </Stack>\n <Grid columns={{ base: 1, md: 3 }} gap={8} className=\"mt-12\">\n {/* Feature cards or content */}\n </Grid>\n </Container>\n</Box>\n\\`\\`\\`\n\n### Component Patterns\n\n**Empty states:**\n\\`\\`\\`tsx\n// GOOD — uses EmptyState component\n<EmptyState\n icon={Inbox}\n title=\"No messages yet\"\n description=\"Messages from your team will appear here.\"\n action={<Button>Send a message</Button>}\n/>\n\n// BAD — hand-rolled empty state\n<div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <Inbox className=\"h-12 w-12 text-gray-400 mb-4\" />\n <h3 className=\"text-lg font-medium\">No messages yet</h3>\n <p className=\"text-gray-500 mt-1\">Messages from your team will appear here.</p>\n</div>\n\\`\\`\\`\n\n**Stats/metrics:**\n\\`\\`\\`tsx\n// GOOD — uses StatCard\n<Grid columns={{ base: 1, sm: 2, lg: 4 }} gap={4}>\n <StatCard label=\"Total Users\" value=\"2,847\" trend=\"+12%\" />\n <StatCard label=\"Revenue\" value=\"$48,290\" trend=\"+8%\" />\n</Grid>\n\n// BAD — hand-rolled stat cards\n<div className=\"grid grid-cols-4 gap-4\">\n <div className=\"bg-white rounded-lg p-6 shadow\">\n <p className=\"text-sm text-gray-500\">Total Users</p>\n <p className=\"text-2xl font-bold\">2,847</p>\n </div>\n</div>\n\\`\\`\\`\n\n**Loading states:**\n\\`\\`\\`tsx\n// GOOD — Skeleton matches the layout structure\n<Stack gap={4}>\n <Skeleton className=\"h-8 w-48\" /> {/* Title */}\n <Skeleton className=\"h-4 w-96\" /> {/* Description */}\n <Grid columns={3} gap={4}>\n {Array.from({ length: 3 }).map((_, i) => (\n <Skeleton key={i} className=\"h-32\" />\n ))}\n </Grid>\n</Stack>\n\n// BAD — generic spinner with no layout hint\n<div className=\"flex justify-center py-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-blue-500 rounded-full\" />\n</div>\n\\`\\`\\`\n\n### Dark Mode & Light Mode\n\n> **CRITICAL: Every screen, component, and custom style MUST look correct in both light and dark mode. No exceptions.**\n\nBlacksmithUI uses the \\`.dark\\` class strategy on \\`<html>\\`. All semantic CSS variables automatically switch between light and dark values. Your job is to never break this.\n\n**Rules:**\n- NEVER hardcode colors. \\`text-gray-500\\`, \\`bg-white\\`, \\`bg-slate-900\\`, \\`border-gray-200\\` — all of these break in one mode or the other. Use semantic tokens: \\`text-muted-foreground\\`, \\`bg-background\\`, \\`bg-card\\`, \\`border-border\\`.\n- NEVER use \\`bg-white\\` or \\`bg-black\\`. Use \\`bg-background\\` (page), \\`bg-card\\` (elevated surfaces), \\`bg-muted\\` (subtle sections).\n- NEVER use \\`text-black\\` or \\`text-white\\`. Use \\`text-foreground\\` (primary text), \\`text-muted-foreground\\` (secondary), \\`text-primary-foreground\\` (text on primary-colored backgrounds).\n- NEVER use hardcoded shadows like \\`shadow-[0_2px_8px_rgba(0,0,0,0.1)]\\`. Use Tailwind shadow utilities (\\`shadow-sm\\`, \\`shadow-md\\`) which respect the theme.\n- NEVER use opacity-based overlays with hardcoded colors (\\`bg-black/50\\`). Use \\`bg-background/80\\` or let overlay components (\\`Dialog\\`, \\`Sheet\\`) handle it.\n- SVG fills and strokes: use \\`currentColor\\` or \\`fill-foreground\\` / \\`stroke-border\\` — never \\`fill-black\\` or \\`stroke-gray-300\\`.\n- Image assets: if you use decorative images or illustrations, ensure they work on both backgrounds or use \\`dark:hidden\\` / \\`hidden dark:block\\` to swap variants.\n\n**Safe color tokens (always use these):**\n| Need | Light mode maps to | Dark mode maps to | Use |\n|------|----|----|-----|\n| Page background | white/light gray | near-black | \\`bg-background\\` |\n| Card/surface | white | dark gray | \\`bg-card\\` |\n| Subtle background | light gray | darker gray | \\`bg-muted\\` |\n| Primary text | near-black | near-white | \\`text-foreground\\` |\n| Secondary text | medium gray | lighter gray | \\`text-muted-foreground\\` |\n| Borders | light gray | dark gray | \\`border-border\\` |\n| Input borders | light gray | dark gray | \\`border-input\\` |\n| Focus ring | brand color | brand color | \\`ring-ring\\` |\n| Primary action | brand color | brand color | \\`bg-primary text-primary-foreground\\` |\n| Destructive | red | red | \\`bg-destructive text-destructive-foreground\\` |\n\n**Testing checklist (mental model):**\nBefore considering any UI complete, verify these in your head:\n1. Does every text element use \\`foreground\\`, \\`muted-foreground\\`, or \\`*-foreground\\` tokens?\n2. Does every background use \\`background\\`, \\`card\\`, \\`muted\\`, or \\`primary\\`/\\`secondary\\`/\\`accent\\` tokens?\n3. Does every border use \\`border\\`, \\`input\\`, or \\`ring\\` tokens?\n4. Are there ANY hex values, rgb values, or Tailwind color names (gray, slate, blue, etc.) in the code? If yes, replace them.\n5. Do hover/focus/active states also use semantic tokens? (\\`hover:bg-muted\\` not \\`hover:bg-gray-100\\`)\n\n### Interactions & Feedback\n\n- **Hover states**: Subtle background change (\\`hover:bg-muted\\`) — not color shifts or scale transforms\n- **Focus**: Use focus-visible ring (\\`focus-visible:ring-2 ring-ring\\`). BlacksmithUI components handle this automatically\n- **Transitions**: \\`transition-colors duration-150\\` for color changes. No bounces, no springs, no dramatic animations\n- **Click feedback**: Use \\`active:scale-[0.98]\\` only on buttons and interactive cards, never on text or static elements\n- **Loading feedback**: Show \\`Spinner\\` on buttons during async actions. Use \\`Skeleton\\` for content areas. Never leave the user without feedback during loading\n- **Success/error feedback**: Use \\`useToast()\\` for transient confirmations. Use \\`Alert\\` for persistent messages. Never use \\`window.alert()\\`\n- **Confirmation before destructive actions**: Always use \\`AlertDialog\\` for delete/remove actions. Never delete on single click\n\n### Responsive Design\n\n- **Mobile-first**: Write base styles for mobile, add \\`sm:\\`/\\`md:\\`/\\`lg:\\` for larger screens\n- **Breakpoints**: \\`sm\\` (640px), \\`md\\` (768px), \\`lg\\` (1024px), \\`xl\\` (1280px)\n- **Grid collapse**: \\`Grid columns={{ base: 1, md: 2, lg: 3 }}\\` — single column on mobile, expand on larger screens\n- **Hide/show**: Use \\`hidden md:block\\` / \\`md:hidden\\` to toggle elements across breakpoints\n- **Touch targets**: Minimum 44×44px for interactive elements on mobile. Use \\`Button size=\"lg\"\\` and adequate padding\n- **Stack on mobile, row on desktop**: Use \\`Flex direction={{ base: 'column', md: 'row' }}\\` or \\`Stack\\` that switches direction\n- **Container**: Always wrap page content in \\`<Container>\\` — it handles responsive horizontal padding\n\n### Anti-Patterns — NEVER Do These\n\n| Anti-pattern | What to do instead |\n|---|---|\n| Hardcoded colors (\\`text-gray-500\\`, \\`bg-blue-600\\`) | Use semantic tokens (\\`text-muted-foreground\\`, \\`bg-primary\\`) |\n| Heavy box shadows (\\`shadow-xl\\`, \\`shadow-2xl\\`) | Use \\`shadow-sm\\` on cards, \\`shadow-md\\` on elevated overlays only |\n| Rounded pill shapes (\\`rounded-full\\`) on cards/containers | Use \\`rounded-lg\\` or \\`rounded-md\\` (controlled by \\`--radius\\`) |\n| Gradient backgrounds on surfaces | Use solid \\`bg-card\\` or \\`bg-background\\` |\n| Decorative borders (\\`border-l-4 border-blue-500\\`) | Use \\`Divider\\` or \\`border-border\\` |\n| Custom scrollbars with CSS hacks | Use \\`ScrollArea\\` |\n| Animated entrances (fade-in, slide-up on mount) | Content should appear instantly. Only animate user-triggered changes |\n| Centering with \\`absolute inset-0 flex items-center\\` | Use \\`Flex align=\"center\" justify=\"center\"\\` |\n| Using \\`<br />\\` for spacing | Use \\`Stack gap={...}\\` or margin utilities |\n| Multiple font sizes in close proximity | Keep nearby text within 1–2 size steps |\n| Dense walls of text | Break into sections with headings, cards, or spacing |\n| Colored backgrounds on every section | Use \\`bg-background\\` as default, \\`bg-muted\\` sparingly for contrast |\n| Over-using badges/tags on everything | Badges are for status and categories, not decoration |\n| Inline styles (\\`style={{ ... }}\\`) | Use Tailwind classes via \\`className\\` |\n| \\`bg-white\\` / \\`bg-black\\` / \\`bg-slate-*\\` | Use \\`bg-background\\`, \\`bg-card\\`, \\`bg-muted\\` |\n| \\`text-black\\` / \\`text-white\\` / \\`text-gray-*\\` | Use \\`text-foreground\\`, \\`text-muted-foreground\\` |\n| \\`border-gray-*\\` / \\`border-slate-*\\` | Use \\`border-border\\`, \\`border-input\\` |\n| Hex/rgb values in className or style | Use CSS variable tokens exclusively |\n| UI that only looks right in light mode | Always verify both modes — use semantic tokens throughout |\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const programmingParadigmsSkill: Skill = {\n id: 'programming-paradigms',\n name: 'Programming Paradigms',\n description: 'Functional programming for React frontend development, OOP + functional patterns for Django backend development.',\n\n render(_ctx: SkillContext): string {\n return `## Programming Paradigms\n\n> **Frontend (React/TypeScript): Functional programming.** Pure functions, immutability, composition, declarative UI.\n> **Backend (Django/Python): OOP with functional touches.** Classes for structure, pure functions for logic, no mutation where avoidable.\n\n---\n\n## Frontend — Functional Programming\n\nReact is a functional framework. Write it functionally. No classes, no imperative mutation, no object-oriented patterns.\n\n### Core Rules\n\n1. **Functions, not classes** — Every component is a function. Every hook is a function. Every utility is a function. Never use \\`class\\` in frontend code.\n2. **Pure by default** — A component given the same props should render the same output. A utility given the same arguments should return the same result. Side effects belong in hooks (\\`useEffect\\`, \\`useMutation\\`), never in render logic.\n3. **Immutable data** — Never mutate state, props, or variables. Always return new values.\n4. **Declarative over imperative** — Describe *what* to render, not *how* to render it. Use \\`map\\`, \\`filter\\`, ternaries, and composition — not \\`for\\` loops, \\`if/else\\` chains, or DOM manipulation.\n5. **Composition over inheritance** — Build complex behavior by composing small functions and components, not by extending base classes.\n\n### Immutability\n\n\\`\\`\\`tsx\n// BAD — mutation\nconst handleAdd = (item) => {\n items.push(item) // mutates array\n setItems(items) // React won't re-render (same reference)\n}\n\nuser.name = 'New Name' // mutates object\nsetUser(user)\n\n// GOOD — immutable updates\nconst handleAdd = (item) => {\n setItems((prev) => [...prev, item])\n}\n\nsetUser((prev) => ({ ...prev, name: 'New Name' }))\n\n// GOOD — immutable array operations\nconst removeItem = (id) => setItems((prev) => prev.filter((i) => i.id !== id))\nconst updateItem = (id, data) => setItems((prev) =>\n prev.map((i) => (i.id === id ? { ...i, ...data } : i))\n)\n\\`\\`\\`\n\n### Pure Functions & Composition\n\n\\`\\`\\`tsx\n// BAD — impure, relies on external state\nlet taxRate = 0.1\nconst calculateTotal = (price) => price * (1 + taxRate)\n\n// GOOD — pure, all inputs explicit\nconst calculateTotal = (price: number, taxRate: number) => price * (1 + taxRate)\n\n// GOOD — compose small functions\nconst formatCurrency = (amount: number) => \\`$\\${amount.toFixed(2)}\\`\nconst calculateTax = (price: number, rate: number) => price * rate\nconst formatPriceWithTax = (price: number, rate: number) =>\n formatCurrency(price + calculateTax(price, rate))\n\\`\\`\\`\n\n### Declarative UI Patterns\n\n\\`\\`\\`tsx\n// BAD — imperative rendering\nfunction UserList({ users }) {\n const items = []\n for (let i = 0; i < users.length; i++) {\n if (users[i].isActive) {\n items.push(<UserCard key={users[i].id} user={users[i]} />)\n }\n }\n return <div>{items}</div>\n}\n\n// GOOD — declarative rendering\nfunction UserList({ users }) {\n return (\n <Stack gap={4}>\n {users\n .filter((user) => user.isActive)\n .map((user) => <UserCard key={user.id} user={user} />)\n }\n </Stack>\n )\n}\n\n// BAD — imperative conditional\nfunction Status({ isOnline }) {\n let badge\n if (isOnline) {\n badge = <Badge>Online</Badge>\n } else {\n badge = <Badge variant=\"secondary\">Offline</Badge>\n }\n return badge\n}\n\n// GOOD — declarative conditional\nfunction Status({ isOnline }) {\n return isOnline\n ? <Badge>Online</Badge>\n : <Badge variant=\"secondary\">Offline</Badge>\n}\n\\`\\`\\`\n\n### Hooks as Functional Composition\n\n\\`\\`\\`tsx\n// BAD — logic in component body\nfunction OrdersPage() {\n const [page, setPage] = useState(1)\n const [search, setSearch] = useState('')\n const debounced = useDebounce(search, 300)\n const { data } = useApiQuery(ordersListOptions({ query: { page, search: debounced } }))\n const deleteOrder = useApiMutation(ordersDestroyMutation())\n\n // ... 20 lines of derived state and handlers\n\n return ( /* JSX */ )\n}\n\n// GOOD — compose hooks, component just renders\nfunction OrdersPage() {\n const { orders, pagination, search, deleteOrder } = useOrdersPage()\n\n return (\n <Stack gap={4}>\n <OrdersToolbar search={search} />\n <OrdersTable orders={orders} onDelete={deleteOrder} />\n <Pagination {...pagination} />\n </Stack>\n )\n}\n\n// The hook composes smaller hooks\nfunction useOrdersPage() {\n const search = useSearchFilter()\n const pagination = usePagination()\n const { data } = useOrders({ page: pagination.page, search: search.debounced })\n const deleteOrder = useDeleteOrder()\n\n return {\n orders: data?.results ?? [],\n pagination: { ...pagination, total: data?.count ?? 0 },\n search,\n deleteOrder: (id: number) => deleteOrder.mutate({ path: { id } }),\n }\n}\n\\`\\`\\`\n\n### Data Transformation — Functional Style\n\n\\`\\`\\`tsx\n// BAD — imperative transformation\nfunction getActiveUserNames(users) {\n const result = []\n for (const user of users) {\n if (user.isActive) {\n result.push(user.name.toUpperCase())\n }\n }\n return result\n}\n\n// GOOD — functional pipeline\nconst getActiveUserNames = (users: User[]) =>\n users\n .filter((u) => u.isActive)\n .map((u) => u.name.toUpperCase())\n\n// GOOD — derive state without mutation\nconst sortedItems = useMemo(\n () => [...items].sort((a, b) => a.name.localeCompare(b.name)),\n [items]\n)\n\nconst groupedByStatus = useMemo(\n () => items.reduce<Record<string, Item[]>>((acc, item) => ({\n ...acc,\n [item.status]: [...(acc[item.status] ?? []), item],\n }), {}),\n [items]\n)\n\\`\\`\\`\n\n### What to Avoid in Frontend Code\n\n| Anti-pattern | Functional alternative |\n|---|---|\n| \\`class MyComponent extends React.Component\\` | \\`function MyComponent()\\` |\n| \\`this.state\\`, \\`this.setState\\` | \\`useState\\`, \\`useReducer\\` |\n| \\`array.push()\\`, \\`object.key = value\\` | Spread: \\`[...arr, item]\\`, \\`{ ...obj, key: value }\\` |\n| \\`for\\` / \\`while\\` loops in render | \\`.map()\\`, \\`.filter()\\`, \\`.reduce()\\` |\n| \\`let\\` for derived values | \\`const\\` + \\`useMemo\\` or inline computation |\n| Mutable ref for state (\\`useRef\\` to track values) | \\`useState\\` or \\`useReducer\\` |\n| HOCs (\\`withAuth\\`, \\`withTheme\\`) | Custom hooks (\\`useAuth\\`, \\`useTheme\\`) |\n| Render props for logic sharing | Custom hooks |\n| \\`if/else\\` chains for rendering | Ternaries, \\`&&\\`, early returns, lookup objects |\n| Singleton services / global mutable state | Context + hooks, React Query for server state |\n\n---\n\n## Backend — OOP with Functional Patterns\n\nDjango is object-oriented by design. Lean into it for structure (models, views, serializers, services), but use functional patterns for pure logic and data transformations.\n\n### OOP for Structure\n\n**Models** — Encapsulate data and behavior together:\n\\`\\`\\`python\nclass Order(TimeStampedModel):\n user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='orders')\n status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')\n total = models.DecimalField(max_digits=10, decimal_places=2, default=0)\n\n class Meta:\n ordering = ['-created_at']\n\n def __str__(self):\n return f\"Order #{self.pk} — {self.user}\"\n\n @property\n def is_cancellable(self):\n return self.status in ('pending', 'confirmed')\n\n def recalculate_total(self):\n self.total = self.items.aggregate(\n total=Sum(F('quantity') * F('unit_price'))\n )['total'] or 0\n self.save(update_fields=['total'])\n\\`\\`\\`\n\n**Services** — Classes for complex business operations with multiple related methods:\n\\`\\`\\`python\nclass OrderService:\n @staticmethod\n @transaction.atomic\n def place_order(*, user, items, shipping_address):\n order = Order.objects.create(user=user, shipping_address=shipping_address)\n for item_data in items:\n OrderItem.objects.create(order=order, **item_data)\n order.recalculate_total()\n NotificationService.send_order_confirmation(order=order)\n return order\n\n @staticmethod\n @transaction.atomic\n def cancel_order(*, order, user):\n if not order.is_cancellable:\n raise ValidationError(\"Order cannot be cancelled in its current state.\")\n if order.user != user:\n raise PermissionDenied(\"You can only cancel your own orders.\")\n order.status = 'cancelled'\n order.save(update_fields=['status'])\n InventoryService.restore_stock(order=order)\n return order\n\\`\\`\\`\n\n**ViewSets** — Inherit, extend, override:\n\\`\\`\\`python\nclass OrderViewSet(ModelViewSet):\n permission_classes = [IsAuthenticated]\n\n def get_queryset(self):\n return OrderSelector.list_for_user(user=self.request.user)\n\n def get_serializer_class(self):\n return {\n 'list': OrderListSerializer,\n 'retrieve': OrderDetailSerializer,\n 'create': OrderCreateSerializer,\n }.get(self.action, OrderUpdateSerializer)\n\n def perform_create(self, serializer):\n serializer.save()\n\\`\\`\\`\n\n**Custom permissions, filters, pagination** — All class-based, inheriting from DRF base classes:\n\\`\\`\\`python\nclass IsOwner(BasePermission):\n def has_object_permission(self, request, view, obj):\n return obj.user == request.user\n\nclass OrderFilterSet(django_filters.FilterSet):\n class Meta:\n model = Order\n fields = ['status', 'created_at']\n\\`\\`\\`\n\n### Functional Patterns in Python\n\nUse functional style for pure logic, data transformation, and utilities — anywhere you don't need state or inheritance.\n\n**Selectors** — Pure query builders (can be functions or static methods):\n\\`\\`\\`python\n# selectors.py — pure functions that build querysets\ndef get_active_orders(*, user, status=None):\n qs = (\n Order.objects\n .filter(user=user, is_active=True)\n .select_related('user')\n .prefetch_related('items__product')\n )\n if status:\n qs = qs.filter(status=status)\n return qs.order_by('-created_at')\n\ndef get_order_summary(*, user):\n return (\n Order.objects\n .filter(user=user)\n .values('status')\n .annotate(count=Count('id'), total=Sum('total'))\n )\n\\`\\`\\`\n\n**Data transformations** — Use comprehensions, \\`map\\`, pure functions:\n\\`\\`\\`python\n# BAD — imperative mutation\ndef format_export_data(orders):\n result = []\n for order in orders:\n row = {}\n row['id'] = order.id\n row['total'] = str(order.total)\n row['items'] = ', '.join([i.product.name for i in order.items.all()])\n result.append(row)\n return result\n\n# GOOD — functional transformation\ndef format_export_data(orders):\n return [\n {\n 'id': order.id,\n 'total': str(order.total),\n 'items': ', '.join(i.product.name for i in order.items.all()),\n }\n for order in orders\n ]\n\\`\\`\\`\n\n**Utility functions** — Pure, no side effects:\n\\`\\`\\`python\n# utils.py — all pure functions\ndef calculate_discount(price: Decimal, percentage: int) -> Decimal:\n return price * (Decimal(percentage) / 100)\n\ndef slugify_unique(name: str, existing_slugs: set[str]) -> str:\n base = slugify(name)\n slug = base\n counter = 1\n while slug in existing_slugs:\n slug = f\"{base}-{counter}\"\n counter += 1\n return slug\n\ndef paginate_list(items: list, page: int, page_size: int = 20) -> list:\n start = (page - 1) * page_size\n return items[start:start + page_size]\n\\`\\`\\`\n\n**Decorators** — Functional composition for cross-cutting concerns:\n\\`\\`\\`python\nimport functools\nimport logging\n\nlogger = logging.getLogger(__name__)\n\ndef log_service_call(func):\n @functools.wraps(func)\n def wrapper(*args, **kwargs):\n logger.info(f\"Calling {func.__name__} with kwargs={kwargs}\")\n result = func(*args, **kwargs)\n logger.info(f\"{func.__name__} completed successfully\")\n return result\n return wrapper\n\n# Usage\nclass OrderService:\n @staticmethod\n @log_service_call\n @transaction.atomic\n def place_order(*, user, items, shipping_address):\n ...\n\\`\\`\\`\n\n### When to Use What\n\n| Pattern | Use OOP (class) | Use Functional (function) |\n|---------|-----------------|---------------------------|\n| **Models** | Always — Django models are classes | Model methods can be property-style pure computations |\n| **Views** | Always — ViewSets, APIViews | — |\n| **Serializers** | Always — DRF serializers are classes | — |\n| **Services** | Business logic with multiple related operations | Single-purpose operations can be standalone functions |\n| **Selectors** | Either — class with static methods or module-level functions | Preferred — pure functions that return querysets |\n| **Permissions** | Always — DRF permissions are class-based | — |\n| **Filters** | Always — django-filter uses classes | — |\n| **Utilities** | Never — don't wrap utilities in classes | Always — pure functions |\n| **Data transforms** | Never | Always — comprehensions, map, pure functions |\n| **Validators** | DRF validator classes for reusable validation | Simple validation functions for one-off checks |\n| **Signals** | Receiver functions (decorated functions) | — |\n| **Tests** | Test classes inheriting APITestCase | Individual test functions with pytest are also fine |\n\n### Backend Anti-Patterns\n\n| Anti-pattern | Correct approach |\n|---|---|\n| God class with 20+ methods | Split into focused Service + Selector + utils |\n| Utility class with only static methods | Use module-level functions instead |\n| Mixin soup (\\`class View(A, B, C, D, E)\\`) | Compose with max 1-2 mixins, prefer explicit overrides |\n| Business logic in views | Move to services |\n| Business logic in serializers | Serializers validate, services execute |\n| Mutable default arguments (\\`def f(items=[])\\`) | Use \\`None\\` default: \\`def f(items=None)\\` → \\`items = items or []\\` |\n| Nested \\`for\\` loops for data building | List/dict comprehensions |\n| Raw SQL for simple queries | Django ORM with \\`annotate\\`, \\`Subquery\\`, \\`F\\` expressions |\n| Global mutable state | Pass dependencies explicitly, use Django settings for config |\n| Deep inheritance chains | Prefer composition, keep inheritance to 1-2 levels |\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const cleanCodeSkill: Skill = {\n id: 'clean-code',\n name: 'Clean Code Principles',\n description: 'Naming, functions, components, file organization, conditionals, error handling, and DRY guidelines.',\n\n render(_ctx: SkillContext): string {\n return `## Clean Code Principles\n\nWrite code that is easy to read, easy to change, and easy to delete. Treat clarity as a feature.\n\n### Naming\n- Names should reveal intent. A reader should understand what a variable, function, or class does without reading its implementation\n- Booleans: prefix with \\`is\\`, \\`has\\`, \\`can\\`, \\`should\\` — e.g. \\`isLoading\\`, \\`hasPermission\\`, \\`canEdit\\`\n- Functions: use verb phrases that describe the action — e.g. \\`fetchUsers\\`, \\`createOrder\\`, \\`validateEmail\\`\n- Event handlers: prefix with \\`handle\\` in components, \\`on\\` in props — e.g. \\`handleSubmit\\`, \\`onSubmit\\`\n- Collections: use plural nouns — e.g. \\`users\\`, \\`orderItems\\`, not \\`userList\\` or \\`data\\`\n- Avoid abbreviations. \\`transaction\\` not \\`txn\\`, \\`button\\` not \\`btn\\`, \\`message\\` not \\`msg\\`\n- Avoid generic names like \\`data\\`, \\`info\\`, \\`item\\`, \\`result\\`, \\`value\\` unless the scope is trivially small (e.g. a one-line callback)\n\n### Functions\n- A function should do one thing. If you can describe what it does with \"and\", split it\n- Keep functions short — aim for under 20 lines. If a function is longer, look for sections you can extract\n- Prefer early returns to reduce nesting. Guard clauses at the top, happy path at the bottom\n- Limit parameters to 3. Beyond that, pass an options object\n- Pure functions are easier to test, reason about, and reuse. Prefer them where possible\n- Don't use flags (boolean parameters) to make a function do two different things — write two functions instead\n\n### Components (React-specific)\n- One component per file. The file name should match the component name\n- Keep components under 100 lines of JSX. Extract sub-components when they grow beyond this\n- Separate data logic (hooks) from presentation (components). A component should mostly be JSX, not logic\n- **Page components are orchestrators** — they should be ~20-30 lines, composing child components from \\`components/\\` and calling hooks from \\`hooks/\\`. Never build a 200-line page monolith\n- Props interfaces should be explicit and narrow — accept only what the component needs, not entire objects\n- Avoid prop drilling beyond 2 levels — use context or restructure the component tree\n- Destructure props in the function signature for clarity\n- Use \\`@blacksmith-ui/react\\` layout components (\\`Stack\\`, \\`Flex\\`, \\`Grid\\`) — never raw \\`<div>\\` with flex/grid classes\n\n### File Organization\n- Keep files short. If a file exceeds 200 lines, it is likely doing too much — split it\n- Group by feature, not by type. \\`features/orders/\\` is better than \\`components/\\`, \\`hooks/\\`, \\`utils/\\` at the top level\n- Co-locate related code. A component's hook, types, and test should live next to it\n- One export per file for components and hooks. Use \\`index.ts\\` barrel files only at the feature boundary\n\n### Conditionals & Logic\n- Prefer positive conditionals: \\`if (isValid)\\` over \\`if (!isInvalid)\\`\n- Extract complex conditions into well-named variables or functions:\n \\`\\`\\`ts\n // Bad\n if (user.role === 'admin' && user.isActive && !user.isSuspended) { ... }\n\n // Good\n const canAccessAdminPanel = user.role === 'admin' && user.isActive && !user.isSuspended\n if (canAccessAdminPanel) { ... }\n \\`\\`\\`\n- Avoid deeply nested if/else trees. Use early returns, guard clauses, or lookup objects\n- Prefer \\`switch\\` or object maps over long \\`if/else if\\` chains:\n \\`\\`\\`ts\n // Bad\n if (status === 'active') return 'green'\n else if (status === 'pending') return 'yellow'\n else if (status === 'inactive') return 'gray'\n\n // Good\n const statusColor = { active: 'green', pending: 'yellow', inactive: 'gray' }\n return statusColor[status]\n \\`\\`\\`\n\n### Error Handling\n- Handle errors at the right level — close to where they occur and where you can do something meaningful\n- Provide useful error messages that help the developer (or user) understand what went wrong and what to do\n- Don't swallow errors silently. If you catch, log or handle. Never write empty \\`catch {}\\` blocks\n- Use typed errors. In Python, raise specific exceptions. In TypeScript, return discriminated unions or throw typed errors\n\n### Comments\n- Don't comment what the code does — make the code readable enough to not need it\n- Do comment why — explain business decisions, workarounds, non-obvious constraints\n- Delete commented-out code. Version control remembers it\n- TODOs are acceptable but should include context: \\`// TODO(auth): rate-limit login attempts after v1 launch\\`\n\n### DRY Without Overengineering\n- Don't repeat the same logic in multiple places — extract it once you see the third occurrence\n- But don't over-abstract. Two similar blocks of code are fine if they serve different purposes and are likely to diverge\n- Premature abstraction is worse than duplication. Wait for patterns to emerge before creating shared utilities\n- Helper functions should be genuinely reusable. A \"helper\" called from one place is just indirection\n\n### Python-Specific (Django)\n- Use \\`f-strings\\` for string formatting, not \\`.format()\\` or \\`%\\`\n- Use list/dict/set comprehensions when they are clearer than loops — but don't nest them\n- Use \\`dataclasses\\` or typed dicts for structured data outside of Django models\n- Keep view methods thin — push business logic into model methods, serializer validation, or service functions\n- Use \\`get_object_or_404\\` instead of manual \\`try/except DoesNotExist\\`\n\n### TypeScript-Specific (React)\n- Use strict TypeScript. Don't use \\`any\\` — use \\`unknown\\` and narrow, or define a proper type\n- Define interfaces for component props, API responses, and form schemas\n- Use \\`const\\` by default. Only use \\`let\\` when reassignment is necessary. Never use \\`var\\`\n- Prefer \\`map\\`, \\`filter\\`, \\`reduce\\` over imperative loops for data transformation\n- Use optional chaining (\\`?.\\`) and nullish coalescing (\\`??\\`) instead of manual null checks\n- Keep type definitions close to where they are used. Don't create a global \\`types.ts\\` file\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const aiGuidelinesSkill: Skill = {\n id: 'ai-guidelines',\n name: 'AI Development Guidelines',\n description: 'Guidelines for developing the project using AI, including when to use code generation, code style, environment setup, and a checklist before finishing tasks.',\n\n render(_ctx: SkillContext): string {\n return `## AI Development Guidelines\n\n### When Adding Features\n1. Use \\`blacksmith make:resource <Name>\\` for new CRUD resources — it scaffolds model, serializer, viewset, URLs, hooks, components, and pages across both backend and frontend\n2. After any backend API change (new endpoint, changed schema, new field), run \\`blacksmith sync\\` to regenerate the frontend API client and types\n3. Never manually edit files in \\`frontend/src/api/generated/\\` — they are overwritten on every sync\n\n### Code Style\n- **Backend**: Follow PEP 8. Use Django and DRF conventions. Docstrings on models, serializers, and non-obvious view methods\n- **Frontend**: TypeScript strict mode. Functional components. Named exports (not default, except for page components used in routes). Descriptive variable names\n- Use existing patterns in the codebase as reference before inventing new ones\n\n### Frontend Architecture (Mandatory)\n- **Use \\`@blacksmith-ui/react\\` for ALL UI** — \\`Stack\\`, \\`Flex\\`, \\`Grid\\` for layout; \\`Typography\\`, \\`Text\\` for text; \\`Card\\`, \\`Button\\`, \\`Badge\\`, etc. for all elements. Never use raw HTML (\\`<div>\\`, \\`<h1>\\`, \\`<p>\\`, \\`<button>\\`) when a Blacksmith-UI component exists\n- **Pages are thin orchestrators** — compose child components from \\`components/\\`, extract logic into \\`hooks/\\`. A page file should be ~20-30 lines, not a monolith\n- **Use the \\`Path\\` enum** — all route paths come from \\`src/router/paths.ts\\`. Never hardcode path strings like \\`'/login'\\` or \\`'/dashboard'\\`\n- **Add new paths to the enum** — when creating a new page, add its path to the \\`Path\\` enum before the \\`// blacksmith:path\\` marker\n\n### Environment\n- Backend: \\`http://localhost:8000\\`\n- Frontend: \\`http://localhost:5173\\`\n- API docs: \\`http://localhost:8000/api/docs/\\` (Swagger UI) or \\`/api/redoc/\\` (ReDoc)\n- Python venv: \\`backend/venv/\\` — always use \\`./venv/bin/python\\` or \\`./venv/bin/pip\\`\n- Start everything: \\`blacksmith dev\\`\n\n### Checklist Before Finishing a Task\n1. Backend tests pass: \\`cd backend && ./venv/bin/python manage.py test\\`\n2. Frontend builds: \\`cd frontend && npm run build\\`\n3. API types are in sync: \\`blacksmith sync\\`\n4. No lint errors in modified files\n5. All UI uses \\`@blacksmith-ui/react\\` components — no raw \\`<div>\\` for layout, no raw \\`<h1>\\`-\\`<h6>\\` for text\n6. Pages are modular — page file is a thin orchestrator, sections are in \\`components/\\`, logic in \\`hooks/\\`\n7. Logic is in hooks — no \\`useApiQuery\\`, \\`useApiMutation\\`, \\`useEffect\\`, or multi-\\`useState\\` in component bodies\n8. No hardcoded route paths — all paths use the \\`Path\\` enum from \\`@/router/paths\\`\n9. New routes have a corresponding \\`Path\\` enum entry\n`\n },\n}\n","import net from 'node:net'\nimport concurrently from 'concurrently'\nimport { findProjectRoot, getBackendDir, getFrontendDir, loadConfig } from '../utils/paths.js'\nimport path from 'node:path'\nimport { log } from '../utils/logger.js'\n\nfunction isPortAvailable(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const server = net.createServer()\n server.once('error', () => resolve(false))\n server.once('listening', () => {\n server.close(() => resolve(true))\n })\n server.listen(port)\n })\n}\n\nasync function findAvailablePort(startPort: number): Promise<number> {\n let port = startPort\n while (port < startPort + 100) {\n if (await isPortAvailable(port)) return port\n port++\n }\n throw new Error(`No available port found in range ${startPort}-${port - 1}`)\n}\n\nexport async function dev() {\n let root: string\n try {\n root = findProjectRoot()\n } catch {\n log.error('Not inside a Blacksmith project. Run \"blacksmith init <name>\" first.')\n process.exit(1)\n }\n\n const config = loadConfig(root)\n const backendDir = getBackendDir(root)\n const frontendDir = getFrontendDir(root)\n\n let backendPort: number\n let frontendPort: number\n try {\n ;[backendPort, frontendPort] = await Promise.all([\n findAvailablePort(config.backend.port),\n findAvailablePort(config.frontend.port),\n ])\n } catch (err) {\n log.error((err as Error).message)\n process.exit(1)\n }\n\n if (backendPort !== config.backend.port) {\n log.step(`Backend port ${config.backend.port} in use, using ${backendPort}`)\n }\n if (frontendPort !== config.frontend.port) {\n log.step(`Frontend port ${config.frontend.port} in use, using ${frontendPort}`)\n }\n\n log.info('Starting development servers...')\n log.blank()\n log.step(`Django → http://localhost:${backendPort}`)\n log.step(`Vite → http://localhost:${frontendPort}`)\n log.step(`Swagger → http://localhost:${backendPort}/api/docs/`)\n log.step('OpenAPI sync → watching backend .py files')\n log.blank()\n\n // Build an inline watcher script that watches backend .py files.\n // Runs as a separate child process via concurrently so fs.watch works reliably.\n const syncCmd = `${process.execPath} ${path.join(frontendDir, 'node_modules', '.bin', 'openapi-ts')}`\n const watcherCode = [\n `const{watch}=require(\"fs\"),{exec}=require(\"child_process\");`,\n `let t=null,s=false;`,\n `watch(${JSON.stringify(backendDir)},{recursive:true},(e,f)=>{`,\n `if(!f||!f.endsWith(\".py\"))return;`,\n `if(f.startsWith(\"venv/\")||f.includes(\"__pycache__\")||f.includes(\"/migrations/\"))return;`,\n `if(t)clearTimeout(t);`,\n `t=setTimeout(()=>{`,\n `if(s)return;s=true;`,\n `console.log(\"Backend change detected — syncing OpenAPI types...\");`,\n `exec(${JSON.stringify(syncCmd)},{cwd:${JSON.stringify(frontendDir)}},(err,o,se)=>{`,\n `s=false;`,\n `if(err)console.error(\"Sync failed:\",se||err.message);`,\n `else console.log(\"OpenAPI types synced\");`,\n `})`,\n `},2000)});`,\n `console.log(\"Watching for .py changes...\");`,\n ].join('')\n\n const { result } = concurrently(\n [\n {\n command: `./venv/bin/python manage.py runserver 0.0.0.0:${backendPort}`,\n name: 'django',\n cwd: backendDir,\n prefixColor: 'green',\n },\n {\n command: 'npm run dev',\n name: 'vite',\n cwd: frontendDir,\n prefixColor: 'blue',\n },\n {\n command: `node -e '${watcherCode}'`,\n name: 'sync',\n cwd: frontendDir,\n prefixColor: 'yellow',\n },\n ],\n {\n prefix: 'name',\n killOthers: ['failure'],\n restartTries: 3,\n }\n )\n\n const shutdown = () => {\n log.blank()\n log.info('Development servers stopped.')\n process.exit(0)\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n\n try {\n await result\n } catch {\n // concurrently rejects when processes are killed\n }\n}\n","import path from 'node:path'\nimport fs from 'node:fs'\nimport { findProjectRoot, getBackendDir, getFrontendDir } from '../utils/paths.js'\nimport { exec, execPython } from '../utils/exec.js'\nimport { log, spinner } from '../utils/logger.js'\n\nexport async function sync() {\n let root: string\n try {\n root = findProjectRoot()\n } catch {\n log.error('Not inside a Blacksmith project. Run \"blacksmith init <name>\" first.')\n process.exit(1)\n }\n\n const backendDir = getBackendDir(root)\n const frontendDir = getFrontendDir(root)\n const s = spinner('Syncing OpenAPI schema to frontend...')\n\n try {\n // Generate schema offline using drf-spectacular management command\n const schemaPath = path.join(frontendDir, '_schema.yml')\n await execPython(['manage.py', 'spectacular', '--file', schemaPath], backendDir, true)\n\n // Temporarily update the openapi-ts config to use the local schema file\n const configPath = path.join(frontendDir, 'openapi-ts.config.ts')\n const configBackup = fs.readFileSync(configPath, 'utf-8')\n const configWithFile = configBackup.replace(\n /path:\\s*['\"]http[^'\"]+['\"]/,\n `path: './_schema.yml'`\n )\n fs.writeFileSync(configPath, configWithFile, 'utf-8')\n\n try {\n await exec(process.execPath, [path.join(frontendDir, 'node_modules', '.bin', 'openapi-ts')], {\n cwd: frontendDir,\n silent: true,\n })\n } finally {\n // Always restore the original config and clean up the schema file\n fs.writeFileSync(configPath, configBackup, 'utf-8')\n if (fs.existsSync(schemaPath)) fs.unlinkSync(schemaPath)\n }\n\n s.succeed('Frontend types, schemas, and hooks synced from OpenAPI spec')\n log.blank()\n log.step('Generated files in frontend/src/api/generated/:')\n log.step(' types.gen.ts → TypeScript interfaces')\n log.step(' zod.gen.ts → Zod validation schemas')\n log.step(' sdk.gen.ts → API client functions')\n log.step(' @tanstack/react-query.gen.ts → TanStack Query hooks')\n log.blank()\n } catch (error: any) {\n s.fail('Failed to sync OpenAPI schema')\n log.error(error.message || error)\n process.exit(1)\n }\n}\n","import path from 'node:path'\nimport fs from 'node:fs'\nimport { findProjectRoot, getBackendDir, getFrontendDir, getTemplatesDir } from '../utils/paths.js'\nimport { generateNames } from '../utils/names.js'\nimport { renderDirectory, appendAfterMarker, insertBeforeMarker } from '../utils/template.js'\nimport { exec, execPython } from '../utils/exec.js'\nimport { log, spinner } from '../utils/logger.js'\n\nexport async function makeResource(name: string) {\n let root: string\n try {\n root = findProjectRoot()\n } catch {\n log.error('Not inside a Blacksmith project. Run \"blacksmith init <name>\" first.')\n process.exit(1)\n }\n\n const names = generateNames(name)\n const backendDir = getBackendDir(root)\n const frontendDir = getFrontendDir(root)\n const templatesDir = getTemplatesDir()\n\n const backendAppDir = path.join(backendDir, 'apps', names.snakes)\n\n // Check if backend resource already exists\n if (fs.existsSync(backendAppDir)) {\n log.error(`Backend app \"${names.snakes}\" already exists.`)\n process.exit(1)\n }\n\n const frontendPageDir = path.join(frontendDir, 'src', 'pages', names.kebabs)\n\n if (fs.existsSync(frontendPageDir)) {\n log.error(`Frontend page \"${names.kebabs}\" already exists.`)\n process.exit(1)\n }\n\n const context = { ...names, projectName: name }\n\n // 1. Generate backend app\n const backendSpinner = spinner(`Creating backend app: apps/${names.snakes}/`)\n try {\n renderDirectory(\n path.join(templatesDir, 'resource', 'backend'),\n backendAppDir,\n context\n )\n backendSpinner.succeed(`Created backend/apps/${names.snakes}/`)\n } catch (error: any) {\n backendSpinner.fail('Failed to create backend app')\n log.error(error.message)\n process.exit(1)\n }\n\n // 2. Register app in settings\n const registerSpinner = spinner('Registering app in Django settings...')\n try {\n const settingsPath = path.join(backendDir, 'config', 'settings', 'base.py')\n appendAfterMarker(\n settingsPath,\n '# blacksmith:apps',\n ` 'apps.${names.snakes}',`\n )\n registerSpinner.succeed('Registered in INSTALLED_APPS')\n } catch (error: any) {\n registerSpinner.fail('Failed to register app in settings')\n log.error(error.message)\n process.exit(1)\n }\n\n // 3. Register URLs\n const urlSpinner = spinner('Registering API URLs...')\n try {\n const urlsPath = path.join(backendDir, 'config', 'urls.py')\n insertBeforeMarker(\n urlsPath,\n '# blacksmith:urls',\n ` path('api/${names.snakes}/', include('apps.${names.snakes}.urls')),`\n )\n urlSpinner.succeed('Registered API URLs')\n } catch (error: any) {\n urlSpinner.fail('Failed to register URLs')\n log.error(error.message)\n process.exit(1)\n }\n\n // 4. Run migrations\n const migrateSpinner = spinner('Running migrations...')\n try {\n await execPython(['manage.py', 'makemigrations', names.snakes], backendDir, true)\n await execPython(['manage.py', 'migrate'], backendDir, true)\n migrateSpinner.succeed('Migrations complete')\n } catch (error: any) {\n migrateSpinner.fail('Migration failed')\n log.error(error.message)\n process.exit(1)\n }\n\n // 5. Sync OpenAPI (generate schema offline, no running Django needed)\n const syncSpinner = spinner('Syncing OpenAPI schema...')\n try {\n const schemaPath = path.join(frontendDir, '_schema.yml')\n await execPython(['manage.py', 'spectacular', '--file', schemaPath], backendDir, true)\n\n const configPath = path.join(frontendDir, 'openapi-ts.config.ts')\n const configBackup = fs.readFileSync(configPath, 'utf-8')\n const configWithFile = configBackup.replace(\n /path:\\s*['\"]http[^'\"]+['\"]/,\n `path: './_schema.yml'`\n )\n fs.writeFileSync(configPath, configWithFile, 'utf-8')\n\n try {\n await exec(process.execPath, [path.join(frontendDir, 'node_modules', '.bin', 'openapi-ts')], {\n cwd: frontendDir,\n silent: true,\n })\n } finally {\n fs.writeFileSync(configPath, configBackup, 'utf-8')\n if (fs.existsSync(schemaPath)) fs.unlinkSync(schemaPath)\n }\n\n syncSpinner.succeed('Frontend types and hooks regenerated')\n } catch {\n syncSpinner.warn('Could not sync OpenAPI. Run \"blacksmith sync\" manually.')\n }\n\n // 6. Generate frontend page\n const frontendSpinner = spinner(`Creating frontend page: pages/${names.kebabs}/`)\n try {\n renderDirectory(\n path.join(templatesDir, 'resource', 'pages'),\n frontendPageDir,\n context\n )\n frontendSpinner.succeed(`Created frontend/src/pages/${names.kebabs}/`)\n } catch (error: any) {\n frontendSpinner.fail('Failed to create frontend page')\n log.error(error.message)\n process.exit(1)\n }\n\n // 7. Register path in paths enum\n const pathSpinner = spinner('Registering route path...')\n try {\n const pathsFile = path.join(frontendDir, 'src', 'router', 'paths.ts')\n insertBeforeMarker(\n pathsFile,\n '// blacksmith:path',\n ` ${names.Names} = '/${names.kebabs}',`\n )\n pathSpinner.succeed('Registered route path')\n } catch {\n pathSpinner.warn('Could not auto-register path. Add it manually to frontend/src/router/paths.ts')\n }\n\n // 8. Register routes in frontend router\n const routeSpinner = spinner('Registering frontend routes...')\n try {\n const routesPath = path.join(frontendDir, 'src', 'router', 'routes.tsx')\n insertBeforeMarker(\n routesPath,\n '// blacksmith:import',\n `import { ${names.names}Routes } from '@/pages/${names.kebabs}'`\n )\n insertBeforeMarker(\n routesPath,\n '// blacksmith:routes',\n ` ...${names.names}Routes,`\n )\n routeSpinner.succeed('Registered frontend routes')\n } catch {\n routeSpinner.warn('Could not auto-register routes. Add them manually to frontend/src/router/routes.tsx')\n }\n\n // 9. Print summary\n log.blank()\n log.success(`Resource \"${names.Name}\" created successfully!`)\n log.blank()\n}\n","import { pascalCase, snakeCase, kebabCase, camelCase } from 'change-case'\nimport pluralize from 'pluralize'\n\nexport interface NameVariants {\n /** PascalCase singular: Post */\n Name: string\n /** PascalCase plural: Posts */\n Names: string\n /** camelCase singular: post */\n name: string\n /** camelCase plural: posts */\n names: string\n /** snake_case singular: post */\n snake: string\n /** snake_case plural: posts */\n snakes: string\n /** kebab-case singular: post */\n kebab: string\n /** kebab-case plural: posts */\n kebabs: string\n /** UPPER_SNAKE singular: POST */\n UPPER: string\n /** UPPER_SNAKE plural: POSTS */\n UPPERS: string\n}\n\n/**\n * Generate all name variants from a singular PascalCase name\n *\n * @example\n * generateNames('BlogPost')\n * // {\n * // Name: 'BlogPost', Names: 'BlogPosts',\n * // name: 'blogPost', names: 'blogPosts',\n * // snake: 'blog_post', snakes: 'blog_posts',\n * // kebab: 'blog-post', kebabs: 'blog-posts',\n * // UPPER: 'BLOG_POST', UPPERS: 'BLOG_POSTS',\n * // }\n */\nexport function generateNames(input: string): NameVariants {\n const singular = pascalCase(input)\n const plural = pluralize(singular)\n\n return {\n Name: singular,\n Names: plural,\n name: camelCase(singular),\n names: camelCase(plural),\n snake: snakeCase(singular),\n snakes: snakeCase(plural),\n kebab: kebabCase(singular),\n kebabs: kebabCase(plural),\n UPPER: snakeCase(singular).toUpperCase(),\n UPPERS: snakeCase(plural).toUpperCase(),\n }\n}\n","import { findProjectRoot, getBackendDir, getFrontendDir } from '../utils/paths.js'\nimport { exec, execPython } from '../utils/exec.js'\nimport { log, spinner } from '../utils/logger.js'\n\nexport async function build() {\n let root: string\n try {\n root = findProjectRoot()\n } catch {\n log.error('Not inside a Blacksmith project. Run \"blacksmith init <name>\" first.')\n process.exit(1)\n }\n\n const backendDir = getBackendDir(root)\n const frontendDir = getFrontendDir(root)\n\n // Build frontend\n const frontendSpinner = spinner('Building frontend...')\n try {\n await exec('npm', ['run', 'build'], { cwd: frontendDir, silent: true })\n frontendSpinner.succeed('Frontend built → frontend/dist/')\n } catch (error: any) {\n frontendSpinner.fail('Frontend build failed')\n log.error(error.message || error)\n process.exit(1)\n }\n\n // Collect static files\n const backendSpinner = spinner('Collecting static files...')\n try {\n await execPython(\n ['manage.py', 'collectstatic', '--noinput'],\n backendDir,\n true\n )\n backendSpinner.succeed('Static files collected')\n } catch (error: any) {\n backendSpinner.fail('Failed to collect static files')\n log.error(error.message || error)\n process.exit(1)\n }\n\n log.blank()\n log.success('Production build complete!')\n log.blank()\n log.step('Frontend assets: frontend/dist/')\n log.step('Backend ready for deployment')\n log.blank()\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport { findProjectRoot } from '../utils/paths.js'\nimport { log } from '../utils/logger.js'\n\nexport async function eject() {\n let root: string\n try {\n root = findProjectRoot()\n } catch {\n log.error('Not inside a Blacksmith project.')\n process.exit(1)\n }\n\n const configPath = path.join(root, 'blacksmith.config.json')\n\n if (fs.existsSync(configPath)) {\n fs.unlinkSync(configPath)\n }\n\n log.success('Blacksmith has been ejected.')\n log.blank()\n log.step('Your project is now a standard Django + React project.')\n log.step('All generated code remains in place and is fully owned by you.')\n log.step('The blacksmith CLI commands will no longer work in this directory.')\n log.blank()\n log.info('To continue development without Blacksmith:')\n log.step('Backend: cd backend && ./venv/bin/python manage.py runserver')\n log.step('Frontend: cd frontend && npm run dev')\n log.step('Codegen: cd frontend && npx openapi-ts')\n log.blank()\n}\n","import fs from 'node:fs'\nimport { findProjectRoot, loadConfig } from '../utils/paths.js'\nimport { setupAiDev } from './ai-setup.js'\nimport { log } from '../utils/logger.js'\n\nimport { projectOverviewSkill } from '../skills/project-overview.js'\nimport { djangoSkill } from '../skills/django.js'\nimport { djangoRestAdvancedSkill } from '../skills/django-rest-advanced.js'\nimport { apiDocumentationSkill } from '../skills/api-documentation.js'\nimport { reactSkill } from '../skills/react.js'\nimport { blacksmithUiReactSkill } from '../skills/blacksmith-ui-react.js'\nimport { blacksmithUiFormsSkill } from '../skills/blacksmith-ui-forms.js'\nimport { blacksmithUiAuthSkill } from '../skills/blacksmith-ui-auth.js'\nimport { blacksmithHooksSkill } from '../skills/blacksmith-hooks.js'\nimport { blacksmithCliSkill } from '../skills/blacksmith-cli.js'\nimport { cleanCodeSkill } from '../skills/clean-code.js'\nimport { aiGuidelinesSkill } from '../skills/ai-guidelines.js'\nimport type { Skill } from '../skills/types.js'\n\nconst allSkills: Skill[] = [\n projectOverviewSkill,\n djangoSkill,\n djangoRestAdvancedSkill,\n apiDocumentationSkill,\n reactSkill,\n blacksmithUiReactSkill,\n blacksmithUiFormsSkill,\n blacksmithUiAuthSkill,\n blacksmithHooksSkill,\n blacksmithCliSkill,\n cleanCodeSkill,\n aiGuidelinesSkill,\n]\n\ninterface SetupOptions {\n blacksmithUiSkill?: boolean\n}\n\nexport async function setupSkills(options: SetupOptions) {\n let root: string\n try {\n root = findProjectRoot()\n } catch {\n log.error('Not inside a Blacksmith project. Run \"blacksmith init <name>\" first.')\n process.exit(1)\n }\n\n const config = loadConfig(root)\n\n await setupAiDev({\n projectDir: root,\n projectName: config.name,\n includeBlacksmithUiSkill: options.blacksmithUiSkill !== false,\n })\n\n log.blank()\n log.success('AI skills generated:')\n log.step(' CLAUDE.md → project overview + guidelines')\n log.step(' .claude/skills/*/SKILL.md → detailed skill files')\n}\n\nexport function listSkills() {\n let root: string\n try {\n root = findProjectRoot()\n } catch {\n log.error('Not inside a Blacksmith project. Run \"blacksmith init <name>\" first.')\n process.exit(1)\n }\n\n const hasClaude = fs.existsSync(`${root}/CLAUDE.md`)\n const hasSkillsDir = fs.existsSync(`${root}/.claude/skills`)\n\n const inlineSkills = allSkills.filter((s) => !s.name)\n const fileSkills = allSkills.filter((s) => s.name)\n\n log.info('Inline skills (in CLAUDE.md):')\n for (const skill of inlineSkills) {\n log.step(` ${skill.id}`)\n }\n\n log.blank()\n log.info('File-based skills (in .claude/skills/):')\n for (const skill of fileSkills) {\n const exists = hasSkillsDir && fs.existsSync(`${root}/.claude/skills/${skill.id}/SKILL.md`)\n const status = exists ? '✓' : '✗'\n log.step(` ${status} ${skill.id}/SKILL.md — ${skill.name}`)\n }\n\n log.blank()\n if (hasClaude && hasSkillsDir) {\n log.success('AI skills are set up. Run \"blacksmith setup:ai\" to regenerate.')\n } else {\n log.info('Run \"blacksmith setup:ai\" to generate AI skills.')\n }\n}\n","import { findProjectRoot, getBackendDir } from '../utils/paths.js'\nimport { log } from '../utils/logger.js'\nimport { execPython } from '../utils/exec.js'\n\nexport async function backend(args: string[]) {\n let root: string\n try {\n root = findProjectRoot()\n } catch {\n log.error('Not inside a Blacksmith project. Run \"blacksmith init <name>\" first.')\n process.exit(1)\n }\n if (args.length === 0) {\n log.error('Please provide a Django management command.')\n log.step('Usage: blacksmith backend <command> [args...]')\n log.step('Example: blacksmith backend createsuperuser')\n process.exit(1)\n }\n\n const backendDir = getBackendDir(root)\n\n try {\n await execPython(['manage.py', ...args], backendDir)\n } catch {\n process.exit(1)\n }\n}\n","import { findProjectRoot, getFrontendDir } from '../utils/paths.js'\nimport { log } from '../utils/logger.js'\nimport { exec } from '../utils/exec.js'\n\nexport async function frontend(args: string[]) {\n let root: string\n try {\n root = findProjectRoot()\n } catch {\n log.error('Not inside a Blacksmith project. Run \"blacksmith init <name>\" first.')\n process.exit(1)\n }\n if (args.length === 0) {\n log.error('Please provide an npm command.')\n log.step('Usage: blacksmith frontend <command> [args...]')\n log.step('Example: blacksmith frontend install axios')\n process.exit(1)\n }\n\n const frontendDir = getFrontendDir(root)\n\n try {\n await exec('npm', args, { cwd: frontendDir })\n } catch {\n process.exit(1)\n }\n}\n"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","names":["path","fs","path","fs","__filename","__dirname","path","fs","path","fs","path","fs","path","path","path","fs","path","fs","path","fs","path","fs","fs","path","path","fs","fs","fs"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/utils/logger.ts","../src/commands/init.ts","../src/utils/template.ts","../src/utils/exec.ts","../src/utils/paths.ts","../src/commands/ai-setup.ts","../src/skills/core-rules.ts","../src/skills/project-overview.ts","../src/skills/django.ts","../src/skills/django-rest-advanced.ts","../src/skills/api-documentation.ts","../src/skills/react.ts","../src/skills/react-query.ts","../src/skills/page-structure.ts","../src/skills/blacksmith-ui-react.ts","../src/skills/blacksmith-ui-forms.ts","../src/skills/blacksmith-ui-auth.ts","../src/skills/blacksmith-hooks.ts","../src/skills/blacksmith-cli.ts","../src/skills/ui-design.ts","../src/skills/programming-paradigms.ts","../src/skills/frontend-testing.ts","../src/skills/clean-code.ts","../src/skills/ai-guidelines.ts","../src/commands/dev.ts","../src/commands/sync.ts","../src/commands/make-resource.ts","../src/utils/names.ts","../src/commands/build.ts","../src/commands/eject.ts","../src/commands/skills.ts","../src/commands/backend.ts","../src/commands/frontend.ts"],"sourcesContent":["import { Command } from 'commander'\nimport { banner } from './utils/logger.js'\nimport { init } from './commands/init.js'\nimport { dev } from './commands/dev.js'\nimport { sync } from './commands/sync.js'\nimport { makeResource } from './commands/make-resource.js'\nimport { build } from './commands/build.js'\nimport { eject } from './commands/eject.js'\nimport { setupSkills, listSkills } from './commands/skills.js'\nimport { backend } from './commands/backend.js'\nimport { frontend } from './commands/frontend.js'\n\nconst program = new Command()\n\nprogram\n .name('blacksmith')\n .description('Fullstack Django + React framework')\n .version('0.1.0')\n .hook('preAction', () => {\n banner()\n })\n\nprogram\n .command('init')\n .argument('[name]', 'Project name')\n .option('--ai', 'Set up AI development skills and documentation (CLAUDE.md)')\n .option('--no-blacksmith-ui-skill', 'Disable blacksmith-ui skill when using --ai')\n .option('-b, --backend-port <port>', 'Django backend port (default: 8000)')\n .option('-f, --frontend-port <port>', 'Vite frontend port (default: 5173)')\n .option('-t, --theme-color <color>', 'Theme color (zinc, slate, blue, green, orange, red, violet)')\n .description('Create a new Blacksmith project')\n .action(init)\n\nprogram\n .command('dev')\n .description('Start development servers (Django + Vite + OpenAPI sync)')\n .action(dev)\n\nprogram\n .command('sync')\n .description('Sync OpenAPI schema to frontend types, schemas, and hooks')\n .action(sync)\n\nprogram\n .command('make:resource')\n .argument('<name>', 'Resource name (PascalCase, e.g. BlogPost)')\n .description('Create a new resource (model, serializer, viewset, hooks, pages)')\n .action(makeResource)\n\nprogram\n .command('build')\n .description('Build both frontend and backend for production')\n .action(build)\n\nprogram\n .command('eject')\n .description('Remove Blacksmith, keep a clean Django + React project')\n .action(eject)\n\nprogram\n .command('setup:ai')\n .description('Generate CLAUDE.md with AI development skills for the project')\n .option('--no-blacksmith-ui-skill', 'Exclude blacksmith-ui skill')\n .action(setupSkills)\n\nprogram\n .command('skills')\n .description('List all available AI development skills')\n .action(listSkills)\n\nprogram\n .command('backend')\n .argument('[args...]', 'Django management command and arguments')\n .description('Run a Django management command (e.g. blacksmith backend createsuperuser)')\n .allowUnknownOption()\n .action(backend)\n\nprogram\n .command('frontend')\n .argument('[args...]', 'npm command and arguments')\n .description('Run an npm command in the frontend (e.g. blacksmith frontend install axios)')\n .allowUnknownOption()\n .action(frontend)\n\nprogram.parse()\n","import chalk from 'chalk'\nimport ora, { type Ora } from 'ora'\nimport { createInterface } from 'node:readline'\n\nexport const log = {\n info: (msg: string) => console.log(chalk.blue('ℹ'), msg),\n success: (msg: string) => console.log(chalk.green('✓'), msg),\n warn: (msg: string) => console.log(chalk.yellow('⚠'), msg),\n error: (msg: string) => console.log(chalk.red('✗'), msg),\n step: (msg: string) => console.log(chalk.cyan('→'), msg),\n blank: () => console.log(),\n}\n\nexport function promptText(label: string, defaultValue?: string): Promise<string> {\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n const def = defaultValue ? chalk.dim(` (${defaultValue})`) : ''\n const question = ` ${chalk.cyan('?')} ${chalk.bold(label)}${def}${chalk.dim(':')} `\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close()\n resolve(answer.trim() || defaultValue || '')\n })\n })\n}\n\nexport function promptYesNo(label: string, defaultValue = false): Promise<boolean> {\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n const hint = defaultValue ? chalk.dim(' (Y/n)') : chalk.dim(' (y/N)')\n const question = ` ${chalk.cyan('?')} ${chalk.bold(label)}${hint}${chalk.dim(':')} `\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close()\n const val = answer.trim().toLowerCase()\n if (!val) return resolve(defaultValue)\n resolve(['y', 'yes'].includes(val))\n })\n })\n}\n\nexport function promptSelect(label: string, options: string[], defaultValue?: string): Promise<string> {\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n const optionList = options.map((opt, i) => `${chalk.dim(` ${i + 1}.`)} ${opt}`).join('\\n')\n const def = defaultValue ? chalk.dim(` (${defaultValue})`) : ''\n const question = ` ${chalk.cyan('?')} ${chalk.bold(label)}${def}\\n${optionList}\\n ${chalk.dim('Choice:')} `\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close()\n const trimmed = answer.trim()\n if (!trimmed && defaultValue) return resolve(defaultValue)\n const index = parseInt(trimmed, 10)\n if (index >= 1 && index <= options.length) return resolve(options[index - 1])\n const match = options.find((opt) => opt.toLowerCase() === trimmed.toLowerCase())\n resolve(match || defaultValue || options[0])\n })\n })\n}\n\nexport function printConfig(config: Record<string, string>) {\n const bar = chalk.dim('│')\n console.log()\n console.log(` ${chalk.dim('┌──────────────────────────────────────┐')}`)\n console.log(` ${bar} ${chalk.bold.white('Configuration')}${' '.repeat(23)}${bar}`)\n console.log(` ${chalk.dim('├──────────────────────────────────────┤')}`)\n for (const [key, value] of Object.entries(config)) {\n const padded = `${chalk.dim(key + ':')} ${chalk.white(value)}`\n const rawLen = `${key}: ${value}`.length\n const padding = ' '.repeat(Math.max(1, 36 - rawLen))\n console.log(` ${bar} ${padded}${padding}${bar}`)\n }\n console.log(` ${chalk.dim('└──────────────────────────────────────┘')}`)\n console.log()\n}\n\nexport function spinner(text: string): Ora {\n return ora({ text, color: 'cyan' }).start()\n}\n\nexport function banner() {\n const logo = [\n ' ██████╗ ██╗ █████╗ ██████╗██╗ ██╗',\n ' ██╔══██╗██║ ██╔══██╗██╔════╝██║ ██╔╝',\n ' ██████╔╝██║ ███████║██║ █████╔╝ ',\n ' ██╔══██╗██║ ██╔══██║██║ ██╔═██╗ ',\n ' ██████╔╝███████╗██║ ██║╚██████╗██║ ██╗',\n ' ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝',\n ' ███████╗███╗ ███╗██╗████████╗██╗ ██╗',\n ' ██╔════╝████╗ ████║██║╚══██╔══╝██║ ██║',\n ' ███████╗██╔████╔██║██║ ██║ ███████║',\n ' ╚════██║██║╚██╔╝██║██║ ██║ ██╔══██║',\n ' ███████║██║ ╚═╝ ██║██║ ██║ ██║ ██║',\n ' ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝',\n ]\n\n console.log()\n for (const line of logo) {\n console.log(chalk.cyan(line))\n }\n console.log()\n console.log(chalk.dim(' Welcome to Blacksmith — forge fullstack apps with one command.'))\n console.log()\n}\n\nexport function printNextSteps(projectName: string, backendPort = 8000, frontendPort = 5173) {\n log.blank()\n log.success('Project created successfully!')\n log.blank()\n console.log(chalk.bold(' Next steps:'))\n console.log()\n console.log(` ${chalk.cyan('cd')} ${projectName}`)\n console.log(` ${chalk.cyan('blacksmith dev')} ${chalk.dim('# Start development servers')}`)\n console.log()\n console.log(chalk.dim(` Django: http://localhost:${backendPort}`))\n console.log(chalk.dim(` React: http://localhost:${frontendPort}`))\n console.log(chalk.dim(` Swagger: http://localhost:${backendPort}/api/docs/`))\n console.log(chalk.dim(` ReDoc: http://localhost:${backendPort}/api/redoc/`))\n log.blank()\n}\n","import path from 'node:path'\nimport fs from 'node:fs'\nimport { spawn } from 'node:child_process'\nimport { renderDirectory } from '../utils/template.js'\nimport { exec, execPython, execPip, commandExists } from '../utils/exec.js'\nimport { getTemplatesDir } from '../utils/paths.js'\nimport { log, spinner, printNextSteps, promptText, promptYesNo, promptSelect, printConfig } from '../utils/logger.js'\nimport { setupAiDev } from './ai-setup.js'\n\nfunction parsePort(value: string, label: string): number {\n const port = parseInt(value, 10)\n if (isNaN(port) || port < 1 || port > 65535) {\n log.error(`Invalid ${label} port: ${value}`)\n process.exit(1)\n }\n return port\n}\n\nconst THEME_PRESETS = ['default', 'blue', 'green', 'violet', 'red', 'neutral']\n\ninterface InitOptions {\n ai?: boolean\n blacksmithUiSkill?: boolean\n backendPort?: string\n frontendPort?: string\n themeColor?: string\n}\n\nexport async function init(name: string | undefined, options: InitOptions) {\n // Interactive prompts for values not provided via flags\n if (!name) {\n name = await promptText('Project name')\n if (!name) {\n log.error('Project name is required.')\n process.exit(1)\n }\n }\n\n if (!options.backendPort) {\n options.backendPort = await promptText('Backend port', '8000')\n }\n\n if (!options.frontendPort) {\n options.frontendPort = await promptText('Frontend port', '5173')\n }\n\n if (!options.themeColor) {\n options.themeColor = await promptSelect('Theme preset', THEME_PRESETS, 'default')\n }\n\n if (options.ai === undefined) {\n options.ai = await promptYesNo('Set up AI coding support')\n }\n\n const backendPort = parsePort(options.backendPort, 'backend')\n const frontendPort = parsePort(options.frontendPort, 'frontend')\n const themePreset = THEME_PRESETS.includes(options.themeColor) ? options.themeColor : 'default'\n\n printConfig({\n 'Project': name,\n 'Backend': `Django on :${backendPort}`,\n 'Frontend': `React on :${frontendPort}`,\n 'Theme': themePreset,\n 'AI support': options.ai ? 'Yes' : 'No',\n })\n\n const projectDir = path.resolve(process.cwd(), name)\n const backendDir = path.join(projectDir, 'backend')\n const frontendDir = path.join(projectDir, 'frontend')\n const templatesDir = getTemplatesDir()\n\n // Validate\n if (fs.existsSync(projectDir)) {\n log.error(`Directory \"${name}\" already exists.`)\n process.exit(1)\n }\n\n // Check prerequisites\n const checkSpinner = spinner('Checking prerequisites...')\n const hasPython = await commandExists('python3')\n const hasNode = await commandExists('node')\n const hasNpm = await commandExists('npm')\n\n if (!hasPython) {\n checkSpinner.fail('Python 3 is required but not found. Install it from https://python.org')\n process.exit(1)\n }\n\n if (!hasNode || !hasNpm) {\n checkSpinner.fail('Node.js and npm are required but not found. Install from https://nodejs.org')\n process.exit(1)\n }\n\n checkSpinner.succeed('Prerequisites OK (Python 3, Node.js, npm)')\n\n const context = {\n projectName: name,\n backendPort,\n frontendPort,\n themePreset,\n }\n\n // 1. Create project directory and config\n fs.mkdirSync(projectDir, { recursive: true })\n fs.writeFileSync(\n path.join(projectDir, 'blacksmith.config.json'),\n JSON.stringify(\n {\n name,\n version: '0.1.0',\n backend: { port: backendPort },\n frontend: { port: frontendPort },\n },\n null,\n 2\n )\n )\n\n // 2. Generate backend\n const backendSpinner = spinner('Generating Django backend...')\n try {\n renderDirectory(\n path.join(templatesDir, 'backend'),\n backendDir,\n context\n )\n\n // Copy .env.example to .env for development\n fs.copyFileSync(\n path.join(backendDir, '.env.example'),\n path.join(backendDir, '.env')\n )\n\n backendSpinner.succeed('Django backend generated')\n } catch (error: any) {\n backendSpinner.fail('Failed to generate backend')\n log.error(error.message)\n process.exit(1)\n }\n\n // 3. Create Python virtual environment\n const venvSpinner = spinner('Creating Python virtual environment...')\n try {\n await exec('python3', ['-m', 'venv', 'venv'], { cwd: backendDir, silent: true })\n venvSpinner.succeed('Virtual environment created')\n } catch (error: any) {\n venvSpinner.fail('Failed to create virtual environment')\n log.error(error.message)\n process.exit(1)\n }\n\n // 4. Install Python dependencies\n const pipSpinner = spinner('Installing Python dependencies...')\n try {\n await execPip(\n ['install', '-r', 'requirements.txt'],\n backendDir,\n true\n )\n pipSpinner.succeed('Python dependencies installed')\n } catch (error: any) {\n pipSpinner.fail('Failed to install Python dependencies')\n log.error(error.message)\n process.exit(1)\n }\n\n // 5. Run Django migrations\n const migrateSpinner = spinner('Running initial migrations...')\n try {\n await execPython(['manage.py', 'makemigrations', 'users'], backendDir, true)\n await execPython(['manage.py', 'migrate'], backendDir, true)\n migrateSpinner.succeed('Database migrated')\n } catch (error: any) {\n migrateSpinner.fail('Failed to run migrations')\n log.error(error.message)\n process.exit(1)\n }\n\n // 6. Generate frontend\n const frontendSpinner = spinner('Generating React frontend...')\n try {\n renderDirectory(\n path.join(templatesDir, 'frontend'),\n frontendDir,\n context\n )\n frontendSpinner.succeed('React frontend generated')\n } catch (error: any) {\n frontendSpinner.fail('Failed to generate frontend')\n log.error(error.message)\n process.exit(1)\n }\n\n // 7. Install Node dependencies\n const npmSpinner = spinner('Installing Node.js dependencies...')\n try {\n await exec('npm', ['install'], { cwd: frontendDir, silent: true })\n npmSpinner.succeed('Node.js dependencies installed')\n } catch (error: any) {\n npmSpinner.fail('Failed to install Node.js dependencies')\n log.error(error.message)\n process.exit(1)\n }\n\n // 8. First OpenAPI sync (start Django temporarily)\n const syncSpinner = spinner('Running initial OpenAPI sync...')\n try {\n // Start Django in background\n const djangoProcess = spawn(\n './venv/bin/python',\n ['manage.py', 'runserver', `0.0.0.0:${backendPort}`, '--noreload'],\n {\n cwd: backendDir,\n stdio: 'ignore',\n detached: true,\n }\n )\n djangoProcess.unref()\n\n // Wait for Django to start\n await new Promise((resolve) => setTimeout(resolve, 4000))\n\n try {\n await exec(process.execPath, [path.join(frontendDir, 'node_modules', '.bin', 'openapi-ts')], { cwd: frontendDir, silent: true })\n syncSpinner.succeed('OpenAPI types synced')\n } catch {\n syncSpinner.warn('OpenAPI sync skipped (run \"blacksmith sync\" after starting Django)')\n }\n\n // Stop Django\n try {\n if (djangoProcess.pid) {\n process.kill(-djangoProcess.pid)\n }\n } catch {\n // Process may have already exited\n }\n } catch {\n syncSpinner.warn('OpenAPI sync skipped (run \"blacksmith sync\" after starting Django)')\n }\n\n // 9. Ensure generated API stub exists (openapi-ts may have cleared the directory)\n const generatedDir = path.join(frontendDir, 'src', 'api', 'generated')\n const stubFile = path.join(generatedDir, 'client.gen.ts')\n if (!fs.existsSync(stubFile)) {\n if (!fs.existsSync(generatedDir)) {\n fs.mkdirSync(generatedDir, { recursive: true })\n }\n fs.writeFileSync(\n stubFile,\n [\n '/**',\n ' * Auto-generated API Client',\n ' *',\n ' * This is a stub file that allows the app to boot before',\n ' * the first OpenAPI sync. Run `blacksmith sync` or `blacksmith dev`',\n ' * to generate the real client from your Django API schema.',\n ' *',\n ' * Generated by Blacksmith. This file will be overwritten by openapi-ts.',\n ' */',\n '',\n \"import { createClient } from '@hey-api/client-fetch'\",\n '',\n 'export const client = createClient()',\n '',\n ].join('\\n'),\n 'utf-8'\n )\n }\n\n // 10. AI development setup (opt-in)\n if (options.ai) {\n await setupAiDev({\n projectDir,\n projectName: name,\n includeBlacksmithUiSkill: options.blacksmithUiSkill !== false,\n })\n }\n\n // 11. Print success\n printNextSteps(name, backendPort, frontendPort)\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport Handlebars from 'handlebars'\n\n/**\n * Register custom Handlebars helpers\n */\nHandlebars.registerHelper('eq', (a: any, b: any) => a === b)\nHandlebars.registerHelper('ne', (a: any, b: any) => a !== b)\nHandlebars.registerHelper('upper', (str: string) => str?.toUpperCase())\nHandlebars.registerHelper('lower', (str: string) => str?.toLowerCase())\n\n/**\n * Render a Handlebars template string with context data.\n * Pre-processes JSX-style braces that collide with Handlebars triple-brace syntax.\n */\nexport function renderTemplate(templateStr: string, context: Record<string, any>): string {\n // Replace literal JSX braces adjacent to Handlebars expressions:\n // `{ {{var}}` → `OPEN_BRACE {{var}}` (prevents `{{{` triple-brace parse)\n // `{{var}}} ` → `{{var}} CLOSE_BRACE` (prevents `}}}` triple-brace parse)\n let safeStr = templateStr\n .replace(/\\{(\\s*)(?=\\{\\{[^{])/g, 'BLACKSMITH_OB$1')\n .replace(/([^}]\\}\\})(\\s*)\\}/g, '$1$2BLACKSMITH_CB')\n\n const template = Handlebars.compile(safeStr, { noEscape: true })\n const rendered = template(context)\n\n return rendered\n .replace(/BLACKSMITH_OB/g, '{')\n .replace(/BLACKSMITH_CB/g, '}')\n}\n\n/**\n * Read a template file and render it with context data\n */\nexport function renderTemplateFile(templatePath: string, context: Record<string, any>): string {\n const templateStr = fs.readFileSync(templatePath, 'utf-8')\n return renderTemplate(templateStr, context)\n}\n\n/**\n * Render a template file and write the output to a destination\n */\nexport function renderToFile(\n templatePath: string,\n destPath: string,\n context: Record<string, any>\n) {\n const rendered = renderTemplateFile(templatePath, context)\n const destDir = path.dirname(destPath)\n\n if (!fs.existsSync(destDir)) {\n fs.mkdirSync(destDir, { recursive: true })\n }\n\n fs.writeFileSync(destPath, rendered, 'utf-8')\n}\n\n/**\n * Recursively render all templates from a source directory to a destination directory.\n * Template files (.hbs) are rendered and written without the .hbs extension.\n * Non-template files are copied as-is.\n * Directory names and file names containing Handlebars expressions are also rendered.\n */\nexport function renderDirectory(\n srcDir: string,\n destDir: string,\n context: Record<string, any>\n) {\n if (!fs.existsSync(srcDir)) {\n throw new Error(`Template directory not found: ${srcDir}`)\n }\n\n const entries = fs.readdirSync(srcDir, { withFileTypes: true })\n\n for (const entry of entries) {\n // Render the name itself (for files like {{kebab}}-form.tsx.hbs)\n const renderedName = renderTemplate(entry.name, context)\n const srcPath = path.join(srcDir, entry.name)\n\n if (entry.isDirectory()) {\n const destSubDir = path.join(destDir, renderedName)\n renderDirectory(srcPath, destSubDir, context)\n } else if (entry.name.endsWith('.hbs')) {\n // Template file: render and write without .hbs extension\n const outputName = renderedName.replace(/\\.hbs$/, '')\n const destPath = path.join(destDir, outputName)\n renderToFile(srcPath, destPath, context)\n } else {\n // Non-template file: copy as-is\n const destPath = path.join(destDir, renderedName)\n const destDirPath = path.dirname(destPath)\n if (!fs.existsSync(destDirPath)) {\n fs.mkdirSync(destDirPath, { recursive: true })\n }\n fs.copyFileSync(srcPath, destPath)\n }\n }\n}\n\n/**\n * Append text to a file after a specific marker line\n */\nexport function appendAfterMarker(\n filePath: string,\n marker: string,\n content: string\n) {\n const fileContent = fs.readFileSync(filePath, 'utf-8')\n const lines = fileContent.split('\\n')\n const markerIndex = lines.findIndex((line) => line.includes(marker))\n\n if (markerIndex === -1) {\n throw new Error(`Marker \"${marker}\" not found in ${filePath}`)\n }\n\n lines.splice(markerIndex + 1, 0, content)\n fs.writeFileSync(filePath, lines.join('\\n'), 'utf-8')\n}\n\n/**\n * Insert text before a specific marker line\n */\nexport function insertBeforeMarker(\n filePath: string,\n marker: string,\n content: string\n) {\n const fileContent = fs.readFileSync(filePath, 'utf-8')\n const lines = fileContent.split('\\n')\n const markerIndex = lines.findIndex((line) => line.includes(marker))\n\n if (markerIndex === -1) {\n throw new Error(`Marker \"${marker}\" not found in ${filePath}`)\n }\n\n lines.splice(markerIndex, 0, content)\n fs.writeFileSync(filePath, lines.join('\\n'), 'utf-8')\n}\n","import { execa } from 'execa'\nimport { log } from './logger.js'\n\nexport interface ExecOptions {\n cwd?: string\n silent?: boolean\n env?: Record<string, string>\n}\n\n/**\n * Execute a shell command and return the result\n */\nexport async function exec(command: string, args: string[], options: ExecOptions = {}) {\n const { cwd, silent = false, env } = options\n\n try {\n const result = await execa(command, args, {\n cwd,\n env: { ...process.env, ...env },\n stdio: silent ? 'pipe' : 'inherit',\n })\n return result\n } catch (error: any) {\n if (!silent) {\n log.error(`Command failed: ${command} ${args.join(' ')}`)\n if (error.stderr) {\n log.error(error.stderr)\n }\n }\n throw error\n }\n}\n\n/**\n * Execute a shell command silently and return stdout\n */\nexport async function execSilent(command: string, args: string[], cwd?: string): Promise<string> {\n const result = await exec(command, args, { cwd, silent: true })\n return result.stdout\n}\n\n/**\n * Check if a command exists in PATH\n */\nexport async function commandExists(command: string): Promise<boolean> {\n try {\n await execa('which', [command], { stdio: 'pipe' })\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Run a Python command using the project's virtual environment\n */\nexport async function execPython(args: string[], cwd: string, silent = false) {\n const venvPython = `${cwd}/venv/bin/python`\n return exec(venvPython, args, { cwd, silent })\n}\n\n/**\n * Run pip using the project's virtual environment\n */\nexport async function execPip(args: string[], cwd: string, silent = false) {\n const venvPip = `${cwd}/venv/bin/pip`\n return exec(venvPip, args, { cwd, silent })\n}\n","import path from 'node:path'\nimport fs from 'node:fs'\nimport { fileURLToPath } from 'node:url'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = path.dirname(__filename)\n\n/**\n * Get the templates directory (relative to the built CLI)\n */\nexport function getTemplatesDir(): string {\n // In development: src/templates\n // In production (built): dist is sibling to src\n const devPath = path.resolve(__dirname, '..', 'templates')\n const prodPath = path.resolve(__dirname, '..', 'src', 'templates')\n\n if (fs.existsSync(devPath)) return devPath\n if (fs.existsSync(prodPath)) return prodPath\n\n throw new Error('Templates directory not found. Make sure the CLI is properly installed.')\n}\n\n/**\n * Find the Blacksmith project root by walking up directories\n * looking for blacksmith.config.json\n */\nexport function findProjectRoot(startDir?: string): string {\n let dir = startDir || process.cwd()\n\n while (dir !== path.dirname(dir)) {\n if (fs.existsSync(path.join(dir, 'blacksmith.config.json'))) {\n return dir\n }\n dir = path.dirname(dir)\n }\n\n throw new Error(\n 'Not inside a Blacksmith project. Run \"blacksmith init <name>\" to create one, or navigate to an existing Blacksmith project.'\n )\n}\n\n/**\n * Get the backend directory of a Blacksmith project\n */\nexport function getBackendDir(projectRoot?: string): string {\n const root = projectRoot || findProjectRoot()\n return path.join(root, 'backend')\n}\n\n/**\n * Get the frontend directory of a Blacksmith project\n */\nexport function getFrontendDir(projectRoot?: string): string {\n const root = projectRoot || findProjectRoot()\n return path.join(root, 'frontend')\n}\n\nexport interface BlacksmithConfig {\n name: string\n version: string\n backend: { port: number }\n frontend: { port: number }\n}\n\nexport function loadConfig(projectRoot?: string): BlacksmithConfig {\n const root = projectRoot || findProjectRoot()\n const configPath = path.join(root, 'blacksmith.config.json')\n return JSON.parse(fs.readFileSync(configPath, 'utf-8'))\n}\n\n/**\n * Check if a directory exists\n */\nexport function dirExists(dirPath: string): boolean {\n return fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()\n}\n\n/**\n * Check if a file exists\n */\nexport function fileExists(filePath: string): boolean {\n return fs.existsSync(filePath) && fs.statSync(filePath).isFile()\n}\n","import path from 'node:path'\nimport fs from 'node:fs'\nimport { log, spinner } from '../utils/logger.js'\nimport type { Skill, SkillContext } from '../skills/types.js'\nimport { coreRulesSkill } from '../skills/core-rules.js'\nimport { projectOverviewSkill } from '../skills/project-overview.js'\nimport { djangoSkill } from '../skills/django.js'\nimport { djangoRestAdvancedSkill } from '../skills/django-rest-advanced.js'\nimport { apiDocumentationSkill } from '../skills/api-documentation.js'\nimport { reactSkill } from '../skills/react.js'\nimport { reactQuerySkill } from '../skills/react-query.js'\nimport { pageStructureSkill } from '../skills/page-structure.js'\nimport { blacksmithUiReactSkill } from '../skills/blacksmith-ui-react.js'\nimport { blacksmithUiFormsSkill } from '../skills/blacksmith-ui-forms.js'\nimport { blacksmithUiAuthSkill } from '../skills/blacksmith-ui-auth.js'\nimport { blacksmithHooksSkill } from '../skills/blacksmith-hooks.js'\nimport { blacksmithCliSkill } from '../skills/blacksmith-cli.js'\nimport { uiDesignSkill } from '../skills/ui-design.js'\nimport { programmingParadigmsSkill } from '../skills/programming-paradigms.js'\nimport { frontendTestingSkill } from '../skills/frontend-testing.js'\nimport { cleanCodeSkill } from '../skills/clean-code.js'\nimport { aiGuidelinesSkill } from '../skills/ai-guidelines.js'\n\ninterface AiSetupOptions {\n projectDir: string\n projectName: string\n includeBlacksmithUiSkill: boolean\n}\n\nexport async function setupAiDev({ projectDir, projectName, includeBlacksmithUiSkill }: AiSetupOptions) {\n const aiSpinner = spinner('Setting up AI development environment...')\n\n try {\n const skills: Skill[] = [\n coreRulesSkill,\n projectOverviewSkill,\n djangoSkill,\n djangoRestAdvancedSkill,\n apiDocumentationSkill,\n reactSkill,\n reactQuerySkill,\n pageStructureSkill,\n ]\n\n if (includeBlacksmithUiSkill) {\n skills.push(blacksmithUiReactSkill)\n skills.push(blacksmithUiFormsSkill)\n skills.push(blacksmithUiAuthSkill)\n skills.push(blacksmithHooksSkill)\n skills.push(uiDesignSkill)\n }\n\n skills.push(blacksmithCliSkill)\n skills.push(frontendTestingSkill)\n skills.push(programmingParadigmsSkill)\n skills.push(cleanCodeSkill)\n skills.push(aiGuidelinesSkill)\n\n const ctx: SkillContext = { projectName }\n\n // Separate inline skills (CLAUDE.md) from file-based skills (.claude/skills/[id]/SKILL.md)\n const inlineSkills = skills.filter((s) => !s.name)\n const fileSkills = skills.filter((s) => s.name)\n\n // Create .claude/skills/ directory (clean existing skill directories first)\n const skillsDir = path.join(projectDir, '.claude', 'skills')\n if (fs.existsSync(skillsDir)) {\n for (const entry of fs.readdirSync(skillsDir)) {\n const entryPath = path.join(skillsDir, entry)\n const stat = fs.statSync(entryPath)\n if (stat.isDirectory()) {\n fs.rmSync(entryPath, { recursive: true })\n } else if (entry.endsWith('.md')) {\n // Clean up legacy flat .md files\n fs.unlinkSync(entryPath)\n }\n }\n }\n fs.mkdirSync(skillsDir, { recursive: true })\n\n // Write each file-based skill to .claude/skills/[id]/SKILL.md with frontmatter\n for (const skill of fileSkills) {\n const skillDir = path.join(skillsDir, skill.id)\n fs.mkdirSync(skillDir, { recursive: true })\n const frontmatter = `---\\nname: ${skill.name}\\ndescription: ${skill.description}\\n---\\n\\n`\n const content = skill.render(ctx).trim()\n fs.writeFileSync(path.join(skillDir, 'SKILL.md'), frontmatter + content + '\\n', 'utf-8')\n }\n\n // Build CLAUDE.md with inline content + skills directory reference\n const inlineContent = inlineSkills.map((s) => s.render(ctx)).join('\\n')\n const skillsList = fileSkills.map((s) => `- \\`.claude/skills/${s.id}/SKILL.md\\` — ${s.name}`).join('\\n')\n\n const claudeMd = [\n inlineContent.trim(),\n '',\n '## AI Skills',\n '',\n 'Detailed skills and conventions are in `.claude/skills/`:',\n '',\n skillsList,\n '',\n 'These files are auto-loaded by Claude Code. Run `blacksmith setup:ai` to regenerate.',\n '',\n ].join('\\n')\n\n fs.writeFileSync(path.join(projectDir, 'CLAUDE.md'), claudeMd, 'utf-8')\n\n const skillNames = skills\n .filter((s) => s.id !== 'project-overview' && s.id !== 'ai-guidelines')\n .map((s) => s.id)\n .join(' + ')\n\n aiSpinner.succeed(`AI dev environment ready (${skillNames} skills)`)\n } catch (error: any) {\n aiSpinner.fail('Failed to set up AI development environment')\n log.error(error.message)\n }\n}\n","import type { Skill, SkillContext } from './types.js'\n\n/**\n * Core Rules — Inlined directly into CLAUDE.md (no `name` property).\n *\n * These are the most critical rules that must always be visible to the AI.\n * They are NOT a separate skill file — they appear at the top of CLAUDE.md.\n */\nexport const coreRulesSkill: Skill = {\n id: 'core-rules',\n // No `name` → content is inlined directly into CLAUDE.md, not a separate file\n\n render(_ctx: SkillContext): string {\n return `## Critical Rules\n\n> **These rules are mandatory. Violating them produces broken, inconsistent code.**\n\n### 1. Use \\`@blacksmith-ui/react\\` for ALL UI\n- **Layout**: Use \\`Stack\\`, \\`Flex\\`, \\`Grid\\`, \\`Box\\`, \\`Container\\` — NEVER \\`<div className=\"flex ...\">\\` or \\`<div className=\"grid ...\">\\`\n- **Typography**: Use \\`Typography\\` and \\`Text\\` — NEVER raw \\`<h1>\\`–\\`<h6>\\`, \\`<p>\\`, or \\`<span>\\` with text classes\n- **Separators**: Use \\`Divider\\` — NEVER \\`<hr>\\` or \\`<Separator>\\`\n- **Everything else**: \\`Button\\`, \\`Card\\`, \\`Badge\\`, \\`Input\\`, \\`Table\\`, \\`Dialog\\`, \\`Alert\\`, \\`Skeleton\\`, \\`EmptyState\\`, \\`StatCard\\`, etc.\n- See the \\`blacksmith-ui-react\\` skill for the full 60+ component list\n\n### 2. Pages Are Thin Orchestrators\n- A page file should be ~20-30 lines: import components, call hooks, compose JSX\n- Break every page into child components in a \\`components/\\` folder\n- See the \\`page-structure\\` skill for the full pattern with examples\n\n### 3. Components Render, Hooks Think\n- Extract ALL logic into hooks in a \\`hooks/\\` folder — API calls, mutations, form setup, filtering, pagination, debouncing, computed state\n- Components should contain only JSX composition, prop passing, and simple event handler wiring\n- The only \\`useState\\` acceptable inline in a component is a simple UI toggle (e.g. modal open/close)\n- If a component has more than one \\`useState\\`, one \\`useEffect\\`, or any \\`useApiQuery\\`/\\`useApiMutation\\` — extract to a hook\n\n### 4. Use the \\`Path\\` Enum — Never Hardcode Paths\n- All route paths are in \\`src/router/paths.ts\\` as a \\`Path\\` enum\n- Use \\`Path.Login\\`, \\`Path.Dashboard\\`, etc. in \\`navigate()\\`, \\`<Link to={}>\\`, and route definitions\n- When adding a new page, add its path to the enum before \\`// blacksmith:path\\`\n- Use \\`buildPath(Path.ResetPassword, { token })\\` for dynamic segments\n\n### 5. Follow the Page/Feature Folder Structure\n\\`\\`\\`\npages/<page>/\n├── <page>.tsx # Thin orchestrator (default export)\n├── routes.tsx # RouteObject[] using Path enum\n├── index.ts # Re-exports public API\n├── components/ # Child components\n└── hooks/ # Page-local hooks (UI logic, not API hooks)\n\\`\\`\\`\n- See the \\`page-structure\\` skill for full conventions\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const projectOverviewSkill: Skill = {\n id: 'project-overview',\n name: 'Project Overview',\n description: 'Overview of the project structure, commands, and development workflow.',\n\n render(ctx: SkillContext): string {\n return `# ${ctx.projectName}\n\nA fullstack web application built with **Django** (backend) and **React** (frontend), scaffolded by **Blacksmith CLI**.\n\n## Project Structure\n\n\\`\\`\\`\n${ctx.projectName}/\n├── backend/ # Django project\n│ ├── apps/ # Django apps (one per resource)\n│ │ └── users/ # Built-in user app\n│ ├── config/ # Django settings, urls, wsgi/asgi\n│ │ └── settings/ # Split settings (base, development, production)\n│ ├── manage.py\n│ ├── requirements.txt\n│ └── venv/ # Python virtual environment\n├── frontend/ # React + Vite project\n│ ├── src/\n│ │ ├── api/ # API client (auto-generated from OpenAPI)\n│ │ ├── features/ # Feature modules (auth, etc.)\n│ │ ├── pages/ # Top-level pages\n│ │ ├── router/ # React Router setup with guards\n│ │ ├── shared/ # Shared components and hooks\n│ │ └── styles/ # Global styles (Tailwind)\n│ ├── package.json\n│ └── tailwind.config.js\n├── blacksmith.config.json\n└── CLAUDE.md # This file\n\\`\\`\\`\n\n## Commands\n\n- \\`blacksmith dev\\` — Start Django + Vite + OpenAPI sync in parallel\n- \\`blacksmith sync\\` — Regenerate frontend API types from Django OpenAPI schema\n- \\`blacksmith make:resource <Name>\\` — Scaffold a full resource (model, serializer, viewset, hooks, pages)\n- \\`blacksmith build\\` — Production build (frontend + collectstatic)\n- \\`blacksmith eject\\` — Remove Blacksmith, keep a clean Django + React project\n\n## Development Workflow\n\n1. Define models in \\`backend/apps/<app>/models.py\\`\n2. Create serializers in \\`backend/apps/<app>/serializers.py\\`\n3. Add viewsets in \\`backend/apps/<app>/views.py\\` and register URLs in \\`backend/apps/<app>/urls.py\\`\n4. Run \\`blacksmith sync\\` to generate TypeScript types and API client\n5. Build frontend features using generated hooks in \\`frontend/src/features/\\`\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const djangoSkill: Skill = {\n id: 'django',\n name: 'Django Backend Conventions',\n description: 'Models, serializers, views, URLs, settings, migrations, and testing patterns for the Django backend.',\n\n render(_ctx: SkillContext): string {\n return `## Django Backend Conventions\n\n### Models\n- Models live in \\`backend/apps/<app>/models.py\\`\n- Use Django's ORM. Inherit from \\`models.Model\\`\n- Use \\`TimeStampedModel\\` pattern: add \\`created_at\\` and \\`updated_at\\` fields with \\`auto_now_add\\` and \\`auto_now\\`\n- Register models in \\`backend/apps/<app>/admin.py\\` for Django admin\n- Use descriptive \\`verbose_name\\` and \\`verbose_name_plural\\` in \\`Meta\\`\n- Define \\`__str__\\` on every model for readable admin and debugging output\n- Use \\`related_name\\` on all ForeignKey and ManyToManyField declarations\n- Prefer \\`TextField\\` over \\`CharField\\` when there is no strict max length requirement\n\n### Serializers\n- Use Django REST Framework serializers in \\`backend/apps/<app>/serializers.py\\`\n- Prefer \\`ModelSerializer\\` for standard CRUD operations\n- Use \\`serializers.Serializer\\` for custom input/output that does not map to a model\n- Add per-field validation via \\`validate_<field>(self, value)\\` methods\n- Add cross-field validation via \\`validate(self, attrs)\\`\n- Use \\`SerializerMethodField\\` for computed read-only fields\n- Nest related serializers for read endpoints; use PrimaryKeyRelatedField for write endpoints\n- Keep serializers thin — move business logic to model methods or service functions\n\n### Views\n- Use DRF \\`ModelViewSet\\` for standard CRUD endpoints\n- Use \\`@action(detail=True|False)\\` decorator for custom non-CRUD endpoints\n- Apply permissions with \\`permission_classes\\` at the class or action level\n- Use \\`@extend_schema\\` from \\`drf-spectacular\\` to document every endpoint — this powers the OpenAPI sync that generates frontend types\n- Use \\`filterset_fields\\`, \\`search_fields\\`, and \\`ordering_fields\\` for queryable list endpoints\n- Override \\`get_queryset()\\` to scope data to the current user when needed\n- Override \\`perform_create()\\` to inject \\`request.user\\` or other context into the serializer save\n\n### URLs\n- Each app has its own \\`urls.py\\` with a \\`DefaultRouter\\`\n- Register viewsets on the router: \\`router.register('resources', ResourceViewSet)\\`\n- App URLs are included in \\`backend/config/urls.py\\` under \\`/api/\\`\n- URL pattern: \\`/api/<resource>/\\` (list/create), \\`/api/<resource>/<id>/\\` (retrieve/update/delete)\n\n### Settings\n- Split settings: \\`base.py\\` (shared), \\`development.py\\` (local dev), \\`production.py\\` (deployment)\n- Environment variables loaded from \\`.env\\` via \\`django-environ\\`\n- Database: SQLite in development, configurable in production via \\`DATABASE_URL\\`\n- \\`INSTALLED_APPS\\` is declared in \\`base.py\\` — add new apps there\n- CORS, allowed hosts, and debug flags are environment-specific\n\n### Migrations\n- Run \\`./venv/bin/python manage.py makemigrations <app>\\` after model changes\n- Run \\`./venv/bin/python manage.py migrate\\` to apply\n- Never edit auto-generated migration files unless resolving a conflict\n- Use \\`RunPython\\` in data migrations for one-time data transformations\n\n### Testing\n- Tests live in \\`backend/apps/<app>/tests.py\\` (or a \\`tests/\\` package for larger apps)\n- Use \\`APITestCase\\` from DRF for API endpoint tests\n- Use \\`APIClient\\` with \\`force_authenticate(user)\\` for authenticated requests\n- Test both success and error paths (400, 401, 403, 404)\n- Run all tests: \\`cd backend && ./venv/bin/python manage.py test\\`\n- Run a single app: \\`cd backend && ./venv/bin/python manage.py test apps.<app>\\`\n\n### Adding a New App Manually\n1. Create the app directory under \\`backend/apps/\\` with \\`__init__.py\\`, \\`models.py\\`, \\`views.py\\`, \\`serializers.py\\`, \\`urls.py\\`, \\`admin.py\\`, \\`tests.py\\`\n2. Add \\`'apps.<app>'\\` to \\`INSTALLED_APPS\\` in \\`backend/config/settings/base.py\\`\n3. Include URLs in \\`backend/config/urls.py\\`: \\`path('api/<app>/', include('apps.<app>.urls'))\\`\n4. Run \\`makemigrations\\` and \\`migrate\\`\n5. Run \\`blacksmith sync\\` to update frontend types\n\n### Common Patterns\n- **Soft delete**: Add an \\`is_active\\` BooleanField and override \\`get_queryset()\\` to filter\n- **Pagination**: Configured globally in \\`REST_FRAMEWORK\\` settings — default is \\`PageNumberPagination\\`\n- **Permissions**: Use \\`IsAuthenticated\\` as default; create custom permissions in \\`permissions.py\\`\n- **Signals**: Use sparingly; prefer explicit calls in serializer/view logic\n- **Management commands**: Place in \\`backend/apps/<app>/management/commands/\\` for CLI tasks\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const djangoRestAdvancedSkill: Skill = {\n id: 'django-rest-advanced',\n name: 'Advanced Django REST Framework',\n description: 'Senior-level DRF patterns: service layer, query optimization, custom permissions, filters, caching, and testing.',\n\n render(_ctx: SkillContext): string {\n return `## Advanced Django REST Framework — Senior-Level Patterns\n\n> **RULE: Follow these patterns for production-grade, scalable, and maintainable DRF APIs.**\n> These build on top of the base Django conventions. Apply them when building non-trivial features.\n\n### Architecture: Service Layer Pattern\n\nKeep views and serializers thin. Extract business logic into service modules.\n\n\\`\\`\\`\nbackend/apps/<app>/\n├── models.py # Data + model-level methods only\n├── serializers.py # Validation + representation only\n├── views.py # HTTP glue + permissions only\n├── services.py # Business logic lives here\n├── selectors.py # Complex read queries\n├── permissions.py # Custom permission classes\n├── filters.py # Custom filter backends\n├── signals.py # Signal handlers (use sparingly)\n├── tasks.py # Celery/background tasks\n└── tests/\n ├── test_views.py\n ├── test_services.py\n └── test_selectors.py\n\\`\\`\\`\n\n\\`\\`\\`python\n# services.py — Business logic\nfrom django.db import transaction\n\nclass OrderService:\n @staticmethod\n @transaction.atomic\n def place_order(*, user, items, shipping_address):\n \"\"\"Place an order with inventory validation and payment.\"\"\"\n order = Order.objects.create(user=user, shipping_address=shipping_address)\n for item in items:\n if item['product'].stock < item['quantity']:\n raise ValidationError(f\"Insufficient stock for {item['product'].name}\")\n OrderItem.objects.create(order=order, **item)\n item['product'].stock -= item['quantity']\n item['product'].save(update_fields=['stock'])\n PaymentService.charge(user=user, amount=order.total)\n return order\n\\`\\`\\`\n\n\\`\\`\\`python\n# selectors.py — Complex read queries\nfrom django.db.models import Q, Count, Prefetch\n\nclass OrderSelector:\n @staticmethod\n def list_for_user(*, user, status=None, search=None):\n qs = (\n Order.objects\n .filter(user=user)\n .select_related('user', 'shipping_address')\n .prefetch_related(\n Prefetch('items', queryset=OrderItem.objects.select_related('product'))\n )\n .annotate(item_count=Count('items'))\n )\n if status:\n qs = qs.filter(status=status)\n if search:\n qs = qs.filter(Q(id__icontains=search) | Q(items__product__name__icontains=search))\n return qs.distinct().order_by('-created_at')\n\\`\\`\\`\n\n### Serializers: Advanced Patterns\n\n**Separate read and write serializers:**\n\\`\\`\\`python\nclass OrderListSerializer(serializers.ModelSerializer):\n \"\"\"Lightweight serializer for list endpoints.\"\"\"\n item_count = serializers.IntegerField(read_only=True)\n user = UserMinimalSerializer(read_only=True)\n\n class Meta:\n model = Order\n fields = ['id', 'status', 'total', 'item_count', 'user', 'created_at']\n\n\nclass OrderDetailSerializer(serializers.ModelSerializer):\n \"\"\"Full serializer for retrieve endpoints.\"\"\"\n items = OrderItemSerializer(many=True, read_only=True)\n user = UserSerializer(read_only=True)\n shipping_address = AddressSerializer(read_only=True)\n\n class Meta:\n model = Order\n fields = ['id', 'status', 'total', 'items', 'user', 'shipping_address', 'created_at', 'updated_at']\n\n\nclass OrderCreateSerializer(serializers.Serializer):\n \"\"\"Write serializer — validates input, delegates to service.\"\"\"\n items = OrderItemInputSerializer(many=True)\n shipping_address_id = serializers.PrimaryKeyRelatedField(queryset=Address.objects.all())\n\n def create(self, validated_data):\n return OrderService.place_order(\n user=self.context['request'].user,\n items=validated_data['items'],\n shipping_address=validated_data['shipping_address_id'],\n )\n\\`\\`\\`\n\n**Writable nested serializers:**\n\\`\\`\\`python\nclass ProjectSerializer(serializers.ModelSerializer):\n tags = TagSerializer(many=True, required=False)\n\n class Meta:\n model = Project\n fields = ['id', 'name', 'description', 'tags']\n\n def create(self, validated_data):\n tags_data = validated_data.pop('tags', [])\n project = Project.objects.create(**validated_data)\n for tag_data in tags_data:\n tag, _ = Tag.objects.get_or_create(**tag_data)\n project.tags.add(tag)\n return project\n\n def update(self, instance, validated_data):\n tags_data = validated_data.pop('tags', None)\n instance = super().update(instance, validated_data)\n if tags_data is not None:\n instance.tags.clear()\n for tag_data in tags_data:\n tag, _ = Tag.objects.get_or_create(**tag_data)\n instance.tags.add(tag)\n return instance\n\\`\\`\\`\n\n**Dynamic field serializers:**\n\\`\\`\\`python\nclass DynamicFieldsSerializer(serializers.ModelSerializer):\n \"\"\"Pass ?fields=id,name,email to limit response fields.\"\"\"\n def __init__(self, *args, **kwargs):\n fields = kwargs.pop('fields', None)\n super().__init__(*args, **kwargs)\n if fields is not None:\n allowed = set(fields)\n for field_name in set(self.fields) - allowed:\n self.fields.pop(field_name)\n\\`\\`\\`\n\n### ViewSets: Advanced Patterns\n\n**Use \\`get_serializer_class()\\` for action-specific serializers:**\n\\`\\`\\`python\nclass OrderViewSet(ModelViewSet):\n permission_classes = [IsAuthenticated]\n filterset_class = OrderFilterSet\n search_fields = ['items__product__name']\n ordering_fields = ['created_at', 'total']\n ordering = ['-created_at']\n\n def get_queryset(self):\n return OrderSelector.list_for_user(user=self.request.user)\n\n def get_serializer_class(self):\n if self.action == 'list':\n return OrderListSerializer\n if self.action == 'retrieve':\n return OrderDetailSerializer\n if self.action in ('create',):\n return OrderCreateSerializer\n return OrderUpdateSerializer\n\n def perform_create(self, serializer):\n serializer.save() # Service called inside serializer.create()\n\n @extend_schema(request=None, responses={200: OrderDetailSerializer})\n @action(detail=True, methods=['post'])\n def cancel(self, request, pk=None):\n order = self.get_object()\n OrderService.cancel_order(order=order, user=request.user)\n return Response(OrderDetailSerializer(order).data)\n\\`\\`\\`\n\n**Bulk operations:**\n\\`\\`\\`python\nclass BulkActionSerializer(serializers.Serializer):\n ids = serializers.ListField(child=serializers.IntegerField(), min_length=1, max_length=100)\n action = serializers.ChoiceField(choices=['archive', 'delete', 'export'])\n\n@extend_schema(request=BulkActionSerializer, responses={200: None})\n@action(detail=False, methods=['post'])\ndef bulk_action(self, request):\n serializer = BulkActionSerializer(data=request.data)\n serializer.is_valid(raise_exception=True)\n qs = self.get_queryset().filter(id__in=serializer.validated_data['ids'])\n action = serializer.validated_data['action']\n if action == 'archive':\n qs.update(status='archived')\n elif action == 'delete':\n qs.delete()\n return Response(status=status.HTTP_200_OK)\n\\`\\`\\`\n\n### QuerySet Optimization\n\n**ALWAYS optimize queries. N+1 queries are unacceptable.**\n\n\\`\\`\\`python\n# BAD — N+1 queries\norders = Order.objects.all()\nfor order in orders:\n print(order.user.email) # 1 query per order\n for item in order.items.all(): # 1 query per order\n print(item.product.name) # 1 query per item\n\n# GOOD — 3 queries total\norders = (\n Order.objects\n .select_related('user')\n .prefetch_related(\n Prefetch('items', queryset=OrderItem.objects.select_related('product'))\n )\n)\n\\`\\`\\`\n\n**Use \\`only()\\` / \\`defer()\\` for large tables:**\n\\`\\`\\`python\n# Only load fields you need for list views\nProduct.objects.only('id', 'name', 'price', 'thumbnail').filter(is_active=True)\n\\`\\`\\`\n\n**Use \\`Subquery\\` and \\`OuterRef\\` for correlated queries:**\n\\`\\`\\`python\nfrom django.db.models import Subquery, OuterRef\n\nlatest_comment = Comment.objects.filter(\n post=OuterRef('pk')\n).order_by('-created_at')\n\nposts = Post.objects.annotate(\n latest_comment_text=Subquery(latest_comment.values('text')[:1])\n)\n\\`\\`\\`\n\n### Custom Permissions\n\n\\`\\`\\`python\n# permissions.py\nfrom rest_framework.permissions import BasePermission\n\nclass IsOwner(BasePermission):\n \"\"\"Object-level permission: only the owner can modify.\"\"\"\n def has_object_permission(self, request, view, obj):\n return obj.user == request.user\n\n\nclass IsAdminOrReadOnly(BasePermission):\n def has_permission(self, request, view):\n if request.method in ('GET', 'HEAD', 'OPTIONS'):\n return True\n return request.user and request.user.is_staff\n\n\nclass HasRole(BasePermission):\n \"\"\"Usage: permission_classes = [HasRole('manager')]\"\"\"\n def __init__(self, role):\n self.role = role\n\n def has_permission(self, request, view):\n return hasattr(request.user, 'role') and request.user.role == self.role\n\\`\\`\\`\n\n**Combine permissions per action:**\n\\`\\`\\`python\nclass ProjectViewSet(ModelViewSet):\n def get_permissions(self):\n if self.action in ('update', 'partial_update', 'destroy'):\n return [IsAuthenticated(), IsOwner()]\n if self.action == 'create':\n return [IsAuthenticated()]\n return [AllowAny()]\n\\`\\`\\`\n\n### Custom Filters with django-filter\n\n\\`\\`\\`python\n# filters.py\nimport django_filters\nfrom .models import Order\n\nclass OrderFilterSet(django_filters.FilterSet):\n min_total = django_filters.NumberFilter(field_name='total', lookup_expr='gte')\n max_total = django_filters.NumberFilter(field_name='total', lookup_expr='lte')\n created_after = django_filters.DateFilter(field_name='created_at', lookup_expr='gte')\n created_before = django_filters.DateFilter(field_name='created_at', lookup_expr='lte')\n status = django_filters.MultipleChoiceFilter(choices=Order.STATUS_CHOICES)\n\n class Meta:\n model = Order\n fields = ['status', 'min_total', 'max_total', 'created_after', 'created_before']\n\\`\\`\\`\n\n### Pagination: Cursor-Based for Large Datasets\n\n\\`\\`\\`python\n# pagination.py\nfrom rest_framework.pagination import CursorPagination\n\nclass TimelinePagination(CursorPagination):\n page_size = 50\n ordering = '-created_at'\n cursor_query_param = 'cursor'\n\\`\\`\\`\n\nUse in viewset: \\`pagination_class = TimelinePagination\\`\n\n### Throttling\n\n\\`\\`\\`python\n# In settings\nREST_FRAMEWORK = {\n 'DEFAULT_THROTTLE_CLASSES': ['rest_framework.throttling.ScopedRateThrottle'],\n 'DEFAULT_THROTTLE_RATES': {\n 'auth': '5/min',\n 'uploads': '20/hour',\n 'burst': '60/min',\n },\n}\n\n# In view\nclass LoginView(APIView):\n throttle_scope = 'auth'\n\\`\\`\\`\n\n### Caching\n\n\\`\\`\\`python\nfrom django.views.decorators.cache import cache_page\nfrom django.utils.decorators import method_decorator\n\nclass ProductViewSet(ModelViewSet):\n @method_decorator(cache_page(60 * 15)) # 15 min cache\n def list(self, request, *args, **kwargs):\n return super().list(request, *args, **kwargs)\n\\`\\`\\`\n\n**Conditional caching with ETags:**\n\\`\\`\\`python\nfrom rest_framework_condition import condition\nfrom hashlib import md5\n\ndef product_etag(request, pk=None):\n product = Product.objects.only('updated_at').get(pk=pk)\n return md5(str(product.updated_at).encode()).hexdigest()\n\nclass ProductViewSet(ModelViewSet):\n @condition(etag_func=product_etag)\n def retrieve(self, request, *args, **kwargs):\n return super().retrieve(request, *args, **kwargs)\n\\`\\`\\`\n\n### Error Handling\n\n\\`\\`\\`python\n# exceptions.py\nfrom rest_framework.views import exception_handler\nfrom rest_framework.response import Response\n\ndef custom_exception_handler(exc, context):\n response = exception_handler(exc, context)\n if response is not None:\n response.data = {\n 'error': {\n 'code': response.status_code,\n 'message': response.data.get('detail', response.data),\n }\n }\n return response\n\\`\\`\\`\n\nRegister in settings: \\`'EXCEPTION_HANDLER': 'config.exceptions.custom_exception_handler'\\`\n\n### Versioning\n\n\\`\\`\\`python\n# settings\nREST_FRAMEWORK = {\n 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',\n 'ALLOWED_VERSIONS': ['v1', 'v2'],\n 'DEFAULT_VERSION': 'v1',\n}\n\n# urls.py\nurlpatterns = [\n path('api/<version>/', include('apps.core.urls')),\n]\n\n# views.py — Version-specific behavior\nclass UserViewSet(ModelViewSet):\n def get_serializer_class(self):\n if self.request.version == 'v2':\n return UserV2Serializer\n return UserV1Serializer\n\\`\\`\\`\n\n### Signals — Use Responsibly\n\n\\`\\`\\`python\n# signals.py — Only for cross-cutting concerns (audit logs, cache invalidation)\nfrom django.db.models.signals import post_save\nfrom django.dispatch import receiver\n\n@receiver(post_save, sender=Order)\ndef notify_on_order_placed(sender, instance, created, **kwargs):\n if created:\n NotificationService.send_order_confirmation(order=instance)\n\\`\\`\\`\n\nRegister in \\`apps.py\\`:\n\\`\\`\\`python\nclass OrdersConfig(AppConfig):\n def ready(self):\n import apps.orders.signals # noqa: F401\n\\`\\`\\`\n\n> **Prefer explicit service calls over signals for business logic.** Signals make flow hard to trace.\n\n### Testing: Senior-Level Patterns\n\n\\`\\`\\`python\nimport factory\nfrom rest_framework.test import APITestCase, APIClient\n\n# factories.py — Use factory_boy for test data\nclass UserFactory(factory.django.DjangoModelFactory):\n class Meta:\n model = User\n email = factory.Sequence(lambda n: f'user{n}@example.com')\n password = factory.PostGenerationMethodCall('set_password', 'testpass123')\n\n\nclass OrderFactory(factory.django.DjangoModelFactory):\n class Meta:\n model = Order\n user = factory.SubFactory(UserFactory)\n status = 'pending'\n\n\n# test_views.py\nclass OrderViewSetTest(APITestCase):\n def setUp(self):\n self.user = UserFactory()\n self.client = APIClient()\n self.client.force_authenticate(self.user)\n\n def test_list_returns_only_own_orders(self):\n OrderFactory.create_batch(3, user=self.user)\n OrderFactory.create_batch(2) # Other user's orders\n response = self.client.get('/api/orders/')\n self.assertEqual(response.status_code, 200)\n self.assertEqual(len(response.data['results']), 3)\n\n def test_create_validates_stock(self):\n product = ProductFactory(stock=0)\n response = self.client.post('/api/orders/', {\n 'items': [{'product_id': product.id, 'quantity': 1}],\n 'shipping_address_id': AddressFactory(user=self.user).id,\n }, format='json')\n self.assertEqual(response.status_code, 400)\n\n def test_cancel_forbidden_for_non_owner(self):\n order = OrderFactory() # Different user\n response = self.client.post(f'/api/orders/{order.id}/cancel/')\n self.assertEqual(response.status_code, 403)\n\\`\\`\\`\n\n**Test query count to prevent N+1 regressions:**\n\\`\\`\\`python\nfrom django.test.utils import override_settings\n\ndef test_list_query_count(self):\n OrderFactory.create_batch(10, user=self.user)\n with self.assertNumQueries(3): # 1 count + 1 orders + 1 prefetch items\n self.client.get('/api/orders/')\n\\`\\`\\`\n\n### API Documentation with drf-spectacular\n\n\\`\\`\\`python\nfrom drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiExample\n\n@extend_schema_view(\n list=extend_schema(\n summary=\"List orders\",\n parameters=[\n OpenApiParameter('status', str, description='Filter by status'),\n OpenApiParameter('search', str, description='Search by product name'),\n ],\n ),\n create=extend_schema(summary=\"Place a new order\"),\n cancel=extend_schema(summary=\"Cancel an order\", responses={200: OrderDetailSerializer}),\n)\nclass OrderViewSet(ModelViewSet):\n ...\n\\`\\`\\`\n\n### Key Principles\n\n1. **Fat services, thin views** — Views handle HTTP; services handle logic\n2. **Optimize every queryset** — Use \\`select_related\\`, \\`prefetch_related\\`, \\`only\\`, \\`annotate\\`\n3. **Separate read/write serializers** — List views are lightweight, detail views are rich, write views validate input\n4. **Test behavior, not implementation** — Test API contracts, permissions, and edge cases\n5. **Use \\`transaction.atomic\\`** — Wrap multi-step mutations to prevent partial writes\n6. **Document with \\`extend_schema\\`** — Every endpoint needs OpenAPI docs for frontend type generation\n7. **Scope querysets to user** — Never return data the user shouldn't see\n8. **Use cursor pagination for large datasets** — Offset pagination degrades at scale\n9. **Throttle sensitive endpoints** — Auth, uploads, and expensive operations\n10. **Version your API** — Plan for breaking changes from the start\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const apiDocumentationSkill: Skill = {\n id: 'api-documentation',\n name: 'API Documentation',\n description: 'drf-spectacular OpenAPI/Swagger documentation conventions for all API endpoints.',\n\n render(_ctx: SkillContext): string {\n return `## API Documentation — drf-spectacular (OpenAPI / Swagger)\n\n> **RULE: Every API endpoint MUST be documented with \\`@extend_schema\\` from \\`drf-spectacular\\`.**\n> Undocumented endpoints break the frontend type generation pipeline (\\`blacksmith sync\\`).\n> The OpenAPI schema powers auto-generated TypeScript types — accurate docs = accurate frontend types.\n\n### Setup\n\ndrf-spectacular is already configured in \\`backend/config/settings/base.py\\`:\n\n\\`\\`\\`python\nINSTALLED_APPS = [\n ...\n 'drf_spectacular',\n]\n\nREST_FRAMEWORK = {\n 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',\n}\n\nSPECTACULAR_SETTINGS = {\n 'TITLE': 'API',\n 'DESCRIPTION': 'API documentation',\n 'VERSION': '1.0.0',\n 'SERVE_INCLUDE_SCHEMA': False,\n}\n\\`\\`\\`\n\nDocs URLs in \\`backend/config/urls.py\\`:\n\\`\\`\\`python\nfrom drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView, SpectacularRedocView\n\nurlpatterns = [\n path('api/schema/', SpectacularAPIView.as_view(), name='schema'),\n path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),\n path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),\n]\n\\`\\`\\`\n\n### Decorating ViewSets — MANDATORY\n\n**Use \\`@extend_schema_view\\` on every ViewSet:**\n\\`\\`\\`python\nfrom drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiExample, OpenApiResponse\n\n@extend_schema_view(\n list=extend_schema(\n summary=\"List projects\",\n description=\"Returns paginated list of projects for the authenticated user.\",\n parameters=[\n OpenApiParameter('status', str, enum=['active', 'archived'], description='Filter by status'),\n OpenApiParameter('search', str, description='Search by name or description'),\n OpenApiParameter('ordering', str, description='Sort field (prefix with - for desc)', enum=['created_at', '-created_at', 'name', '-name']),\n ],\n responses={200: ProjectListSerializer},\n ),\n retrieve=extend_schema(\n summary=\"Get project details\",\n responses={200: ProjectDetailSerializer},\n ),\n create=extend_schema(\n summary=\"Create a project\",\n request=ProjectCreateSerializer,\n responses={201: ProjectDetailSerializer},\n examples=[\n OpenApiExample(\n 'Create project',\n value={'name': 'My Project', 'description': 'A new project'},\n request_only=True,\n ),\n ],\n ),\n update=extend_schema(\n summary=\"Update a project\",\n request=ProjectUpdateSerializer,\n responses={200: ProjectDetailSerializer},\n ),\n partial_update=extend_schema(\n summary=\"Partially update a project\",\n request=ProjectUpdateSerializer,\n responses={200: ProjectDetailSerializer},\n ),\n destroy=extend_schema(\n summary=\"Delete a project\",\n responses={204: None},\n ),\n)\nclass ProjectViewSet(ModelViewSet):\n ...\n\\`\\`\\`\n\n**Custom actions MUST also be decorated:**\n\\`\\`\\`python\n@extend_schema(\n summary=\"Archive a project\",\n request=None,\n responses={200: ProjectDetailSerializer},\n)\n@action(detail=True, methods=['post'])\ndef archive(self, request, pk=None):\n project = self.get_object()\n ProjectService.archive(project=project)\n return Response(ProjectDetailSerializer(project).data)\n\n\n@extend_schema(\n summary=\"Bulk delete projects\",\n request=BulkDeleteSerializer,\n responses={204: None},\n)\n@action(detail=False, methods=['post'])\ndef bulk_delete(self, request):\n ...\n\\`\\`\\`\n\n### Decorating APIViews\n\n\\`\\`\\`python\nclass DashboardStatsView(APIView):\n @extend_schema(\n summary=\"Get dashboard statistics\",\n responses={200: DashboardStatsSerializer},\n )\n def get(self, request):\n stats = DashboardSelector.get_stats(user=request.user)\n return Response(DashboardStatsSerializer(stats).data)\n\\`\\`\\`\n\n### Serializer Documentation\n\n**Use \\`help_text\\` on serializer fields — these become field descriptions in the schema:**\n\\`\\`\\`python\nclass ProjectCreateSerializer(serializers.Serializer):\n name = serializers.CharField(max_length=255, help_text=\"The project name. Must be unique per user.\")\n description = serializers.CharField(required=False, help_text=\"Optional project description.\")\n status = serializers.ChoiceField(\n choices=['active', 'archived'],\n default='active',\n help_text=\"Initial project status.\",\n )\n tags = serializers.ListField(\n child=serializers.CharField(),\n required=False,\n help_text=\"List of tag names to attach.\",\n )\n\\`\\`\\`\n\n**Use \\`@extend_schema_serializer\\` for serializer-level docs:**\n\\`\\`\\`python\nfrom drf_spectacular.utils import extend_schema_serializer, OpenApiExample\n\n@extend_schema_serializer(\n examples=[\n OpenApiExample(\n 'Project response',\n value={\n 'id': 1,\n 'name': 'My Project',\n 'status': 'active',\n 'created_at': '2025-01-15T10:30:00Z',\n },\n response_only=True,\n ),\n ]\n)\nclass ProjectDetailSerializer(serializers.ModelSerializer):\n class Meta:\n model = Project\n fields = ['id', 'name', 'description', 'status', 'created_at', 'updated_at']\n\\`\\`\\`\n\n### Response Types\n\n**Explicitly declare all possible response codes:**\n\\`\\`\\`python\n@extend_schema(\n summary=\"Place an order\",\n request=OrderCreateSerializer,\n responses={\n 201: OrderDetailSerializer,\n 400: OpenApiResponse(description=\"Validation error (insufficient stock, invalid address, etc.)\"),\n 401: OpenApiResponse(description=\"Authentication required\"),\n 403: OpenApiResponse(description=\"Insufficient permissions\"),\n },\n)\ndef create(self, request, *args, **kwargs):\n ...\n\\`\\`\\`\n\n### Enum and Choice Fields\n\n**Use \\`@extend_schema_field\\` for custom field types:**\n\\`\\`\\`python\nfrom drf_spectacular.utils import extend_schema_field\nfrom drf_spectacular.types import OpenApiTypes\n\n@extend_schema_field(OpenApiTypes.STR)\nclass ColorField(serializers.Field):\n ...\n\\`\\`\\`\n\n### Polymorphic / Union Responses\n\n\\`\\`\\`python\nfrom drf_spectacular.utils import PolymorphicProxySerializer\n\n@extend_schema(\n responses=PolymorphicProxySerializer(\n component_name='Notification',\n serializers={\n 'email': EmailNotificationSerializer,\n 'sms': SmsNotificationSerializer,\n 'push': PushNotificationSerializer,\n },\n resource_type_field_name='type',\n )\n)\ndef list(self, request):\n ...\n\\`\\`\\`\n\n### Pagination in Schema\n\ndrf-spectacular auto-wraps list responses with pagination. If using custom pagination:\n\\`\\`\\`python\nfrom drf_spectacular.utils import extend_schema\n\n@extend_schema(\n summary=\"List items\",\n responses=ItemSerializer(many=True), # Pagination wrapper is auto-applied\n)\ndef list(self, request, *args, **kwargs):\n ...\n\\`\\`\\`\n\n### Tags for Grouping\n\n**Group endpoints by feature using tags:**\n\\`\\`\\`python\n@extend_schema_view(\n list=extend_schema(tags=['Orders']),\n create=extend_schema(tags=['Orders']),\n retrieve=extend_schema(tags=['Orders']),\n)\nclass OrderViewSet(ModelViewSet):\n ...\n\\`\\`\\`\n\nOr set a default tag via \\`SPECTACULAR_SETTINGS\\`:\n\\`\\`\\`python\nSPECTACULAR_SETTINGS = {\n 'TAGS': [\n {'name': 'Auth', 'description': 'Authentication endpoints'},\n {'name': 'Orders', 'description': 'Order management'},\n {'name': 'Products', 'description': 'Product catalog'},\n ],\n}\n\\`\\`\\`\n\n### Authentication in Schema\n\n\\`\\`\\`python\nSPECTACULAR_SETTINGS = {\n 'SECURITY': [{'jwtAuth': []}],\n 'APPEND_COMPONENTS': {\n 'securitySchemes': {\n 'jwtAuth': {\n 'type': 'http',\n 'scheme': 'bearer',\n 'bearerFormat': 'JWT',\n }\n }\n },\n}\n\\`\\`\\`\n\n### Excluding Endpoints\n\n\\`\\`\\`python\n@extend_schema(exclude=True)\n@action(detail=False, methods=['get'])\ndef internal_health_check(self, request):\n ...\n\\`\\`\\`\n\n### Generating and Validating the Schema\n\n\\`\\`\\`bash\n# Generate schema file\n./venv/bin/python manage.py spectacular --file schema.yml\n\n# Validate schema for errors\n./venv/bin/python manage.py spectacular --validate\n\\`\\`\\`\n\n> **Always run \\`--validate\\` after adding new endpoints.** Fix any warnings before committing.\n\n### Rules\n\n1. **Every ViewSet** must have \\`@extend_schema_view\\` with summaries for all actions\n2. **Every custom \\`@action\\`** must have its own \\`@extend_schema\\` decorator\n3. **Every serializer field** that isn't self-explanatory must have \\`help_text\\`\n4. **Request and response serializers** must be explicitly declared — do not rely on auto-detection for non-trivial endpoints\n5. **All error responses** (400, 401, 403, 404) should be documented with \\`OpenApiResponse\\`\n6. **Run \\`manage.py spectacular --validate\\`** before committing to catch schema issues early\n7. **Use examples** (\\`OpenApiExample\\`) for complex request/response bodies\n8. **Group endpoints with tags** to keep Swagger UI organized\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const reactSkill: Skill = {\n id: 'react',\n name: 'React Frontend Conventions',\n description: 'Tech stack, project structure, state management, component patterns, styling, and testing for the React frontend.',\n\n render(_ctx: SkillContext): string {\n return `## React Frontend Conventions\n\n### Tech Stack\n- React 19 + TypeScript (strict mode)\n- Vite for bundling and dev server\n- TanStack React Query for server state management\n- React Router v7 for client-side routing\n- React Hook Form + Zod for forms and validation\n- Tailwind CSS for styling\n- \\`@hey-api/openapi-ts\\` for auto-generating API client from Django's OpenAPI schema\n- \\`lucide-react\\` for icons\n\n### API Layer\n- Auto-generated client in \\`frontend/src/api/generated/\\` — **never edit these files manually**\n- Custom API configuration (base URL, interceptors, auth headers) in \\`frontend/src/api/client.ts\\`\n- Query client setup and default options in \\`frontend/src/api/query-client.ts\\`\n- After any backend API change, run \\`blacksmith sync\\` to regenerate the client\n\n### Project Structure\n- See the \\`page-structure\\` skill for page folders, feature modules, routing, and route composition conventions\n- Shared, cross-feature code lives in \\`frontend/src/shared/\\`\n\n### State Management\n- **Server state**: TanStack React Query — see the \\`react-query\\` skill for full conventions on \\`useApiQuery\\` and \\`useApiMutation\\`\n- **Form state**: React Hook Form — manages form values, validation, submission\n- **Local UI state**: React \\`useState\\` / \\`useReducer\\` for component-scoped state\n- Avoid global state libraries unless there is a clear cross-cutting concern not covered by React Query\n\n### Component Patterns\n- Use functional components with named exports (not default exports for components)\n- Co-locate component, hook, and type in the same feature directory\n- Keep components focused — extract sub-components when a file exceeds ~150 lines\n- Use custom hooks to encapsulate data fetching and mutation logic\n- Prefer composition over prop drilling — use context for deeply shared state\n- **Pages must be thin orchestrators** — break into child components in \\`components/\\`, extract logic into \\`hooks/\\`. See the \\`page-structure\\` skill for the full pattern\n\n### UI Components\n- **All UI must use \\`@blacksmith-ui/react\\` components** — see the \\`blacksmith-ui-react\\` skill for the full component list\n- Use \\`Stack\\`, \\`Flex\\`, \\`Grid\\`, \\`Box\\` for layout — never raw \\`<div>\\` with flex/grid classes\n- Use \\`Typography\\` and \\`Text\\` for headings and text — never raw \\`<h1>\\`–\\`<h6>\\` or \\`<p>\\`\n- Use \\`Divider\\` instead of \\`<Separator>\\` or \\`<hr>\\`\n- Use \\`StatCard\\`, \\`EmptyState\\`, \\`Skeleton\\` instead of building custom equivalents\n\n### Route Paths\n- All route paths live in the \\`Path\\` enum at \\`src/router/paths.ts\\` — **never hardcode path strings**\n- Use \\`Path\\` in route definitions, \\`navigate()\\`, and \\`<Link to={}>\\`\n- Use \\`buildPath()\\` for dynamic segments — see the \\`page-structure\\` skill for details\n\n### Styling\n- Use Tailwind CSS utility classes for all styling\n- Use the \\`cn()\\` helper (from \\`clsx\\` + \\`tailwind-merge\\`) for conditional and merged classes\n- Theming via HSL CSS variables defined in \\`frontend/src/styles/globals.css\\`\n- Dark mode is supported via the \\`class\\` strategy on \\`<html>\\`\n- Use responsive prefixes (\\`sm:\\`, \\`md:\\`, \\`lg:\\`) for responsive layouts\n- Avoid inline \\`style\\` attributes — use Tailwind classes instead\n\n### Path Aliases\n- \\`@/\\` maps to \\`frontend/src/\\`\n- Always use the alias for imports: \\`import { useAuth } from '@/features/auth'\\`\n- Never use relative paths that go up more than one level (\\`../../\\`)\n\n### Error Handling\n- Use React Error Boundary (\\`frontend/src/router/error-boundary.tsx\\`) for render errors\n- API errors are handled by \\`useApiQuery\\` / \\`useApiMutation\\` — see the \\`react-query\\` skill for error display patterns\n- Display user-facing errors using the project's feedback components (Alert, Toast)\n\n### Testing\n- See the \\`frontend-testing\\` skill for full conventions on test placement, utilities, mocking, and what to test\n- **Every code change must include corresponding tests** — see the \\`frontend-testing\\` skill for the complete rules\n- Tests use \\`.spec.tsx\\` / \\`.spec.ts\\` and live in \\`__tests__/\\` folders co-located with source code\n- Always use \\`renderWithProviders\\` from \\`@/__tests__/test-utils\\` — never import \\`render\\` from \\`@testing-library/react\\` directly\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const reactQuerySkill: Skill = {\n id: 'react-query',\n name: 'TanStack React Query',\n description: 'API data fetching conventions using useApiQuery and useApiMutation wrappers.',\n\n render(_ctx: SkillContext): string {\n return `## TanStack React Query — API Data Fetching\n\n> **RULE: Always use \\`useApiQuery\\` and \\`useApiMutation\\` instead of raw \\`useQuery\\` / \\`useMutation\\`.**\n> These wrappers live in \\`@/shared/hooks/\\` and handle DRF error parsing, smart retry, and cache invalidation automatically.\n\n### Queries — \\`useApiQuery\\`\n\nImport: \\`import { useApiQuery } from '@/shared/hooks/use-api-query'\\`\n\nWraps \\`useQuery\\` with:\n- **Smart retry** — skips 400, 401, 403, 404, 405, 409, 422 (retries others up to 2 times)\n- **Parsed errors** — \\`errorMessage\\` (string) and \\`apiError\\` (structured) derived from \\`result.error\\`\n\n\\`\\`\\`tsx\n// Basic list query using generated options\nimport { postsListOptions } from '@/api/generated/@tanstack/react-query.gen'\n\nconst { data, isLoading, errorMessage } = useApiQuery({\n ...postsListOptions({ query: { page: 1 } }),\n})\n\n// With select to transform data\nconst { data, errorMessage } = useApiQuery({\n ...postsListOptions({ query: { page: 1 } }),\n select: (data: any) => ({\n posts: data.results ?? [],\n total: data.count ?? 0,\n }),\n})\n\n// Detail query\nimport { postsRetrieveOptions } from '@/api/generated/@tanstack/react-query.gen'\n\nconst { data: post, isLoading, errorMessage } = useApiQuery({\n ...postsRetrieveOptions({ path: { id: id! } }),\n})\n\n// Conditional query (skip until id is available)\nconst { data } = useApiQuery({\n ...postsRetrieveOptions({ path: { id: id! } }),\n enabled: !!id,\n})\n\\`\\`\\`\n\n**Return type extends \\`UseQueryResult\\` with:**\n| Field | Type | Description |\n|-------|------|-------------|\n| \\`errorMessage\\` | \\`string \\\\| null\\` | Human-readable error message |\n| \\`apiError\\` | \\`ApiError \\\\| null\\` | Structured error with \\`status\\`, \\`message\\`, \\`fieldErrors\\` |\n\n### Mutations — \\`useApiMutation\\`\n\nImport: \\`import { useApiMutation } from '@/shared/hooks/use-api-mutation'\\`\n\nWraps \\`useMutation\\` with:\n- **DRF error parsing** — \\`fieldErrors\\`, \\`errorMessage\\`, \\`apiError\\` derived from \\`mutation.error\\` (no local state)\n- **Cache invalidation** — pass \\`invalidateKeys\\` to auto-invalidate queries on success\n\n\\`\\`\\`tsx\n// Create mutation with cache invalidation\nimport {\n postsCreateMutation,\n postsListQueryKey,\n} from '@/api/generated/@tanstack/react-query.gen'\n\nconst createPost = useApiMutation({\n ...postsCreateMutation(),\n invalidateKeys: [postsListQueryKey()],\n})\n\n// Trigger the mutation\ncreatePost.mutate({ body: { title: 'Hello', content: '...' } })\n\n// Update mutation — invalidate both list and detail caches\nimport {\n postsUpdateMutation,\n postsRetrieveQueryKey,\n} from '@/api/generated/@tanstack/react-query.gen'\n\nconst updatePost = useApiMutation({\n ...postsUpdateMutation(),\n invalidateKeys: [\n postsListQueryKey(),\n postsRetrieveQueryKey({ path: { id } }),\n ],\n})\n\n// Delete with async/await\nconst deletePost = useApiMutation({\n ...postsDestroyMutation(),\n invalidateKeys: [postsListQueryKey()],\n})\n\nconst handleDelete = async () => {\n await deletePost.mutateAsync({ path: { id: id! } })\n navigate('/posts')\n}\n\\`\\`\\`\n\n**Return type extends \\`UseMutationResult\\` with:**\n| Field | Type | Description |\n|-------|------|-------------|\n| \\`fieldErrors\\` | \\`Record<string, string[]>\\` | Per-field validation errors from DRF |\n| \\`errorMessage\\` | \\`string \\\\| null\\` | General error message |\n| \\`apiError\\` | \\`ApiError \\\\| null\\` | Full structured error |\n\n### Error Display Patterns\n\n\\`\\`\\`tsx\n// General error banner\n{mutation.errorMessage && (\n <Alert variant=\"destructive\">\n <AlertDescription>{mutation.errorMessage}</AlertDescription>\n </Alert>\n)}\n\n// Inline field errors in forms\nconst getFieldError = (field: string): string | undefined => {\n // Client-side (react-hook-form) errors take priority\n const clientError = form.formState.errors[field]?.message\n if (clientError) return clientError\n // Fall back to server-side field errors\n return mutation.fieldErrors[field]?.[0]\n}\n\n// Query error on a page\nconst { data, isLoading, errorMessage } = useApiQuery({ ... })\n\nif (errorMessage) {\n return (\n <Alert variant=\"destructive\">\n <AlertDescription>{errorMessage}</AlertDescription>\n </Alert>\n )\n}\n\\`\\`\\`\n\n### Creating Resource Hook Files\n\nWhen building hooks for a resource, create two files:\n\n**\\`use-<resources>.ts\\`** — List query hook:\n\\`\\`\\`tsx\nimport { useApiQuery } from '@/shared/hooks/use-api-query'\nimport { postsListOptions } from '@/api/generated/@tanstack/react-query.gen'\n\ninterface UsePostsParams {\n page?: number\n search?: string\n ordering?: string\n}\n\nexport function usePosts(params: UsePostsParams = {}) {\n return useApiQuery({\n ...postsListOptions({\n query: {\n page: params.page ?? 1,\n search: params.search,\n ordering: params.ordering ?? '-created_at',\n },\n }),\n select: (data: any) => ({\n posts: data.results ?? [],\n total: data.count ?? 0,\n hasNext: !!data.next,\n hasPrev: !!data.previous,\n }),\n })\n}\n\\`\\`\\`\n\n**\\`use-<resource>-mutations.ts\\`** — Create/update/delete hooks:\n\\`\\`\\`tsx\nimport { useApiMutation } from '@/shared/hooks/use-api-mutation'\nimport {\n postsCreateMutation,\n postsUpdateMutation,\n postsDestroyMutation,\n postsListQueryKey,\n postsRetrieveQueryKey,\n} from '@/api/generated/@tanstack/react-query.gen'\n\nexport function useCreatePost() {\n return useApiMutation({\n ...postsCreateMutation(),\n invalidateKeys: [postsListQueryKey()],\n })\n}\n\nexport function useUpdatePost(id: string) {\n return useApiMutation({\n ...postsUpdateMutation(),\n invalidateKeys: [\n postsListQueryKey(),\n postsRetrieveQueryKey({ path: { id } }),\n ],\n })\n}\n\nexport function useDeletePost() {\n return useApiMutation({\n ...postsDestroyMutation(),\n invalidateKeys: [postsListQueryKey()],\n })\n}\n\\`\\`\\`\n\n### Key Rules\n\n1. **Never use raw \\`useQuery\\` or \\`useMutation\\`** — always go through \\`useApiQuery\\` / \\`useApiMutation\\`\n2. **Never manage API error state with \\`useState\\`** — error state is derived from TanStack Query's \\`error\\` field\n3. **Always pass \\`invalidateKeys\\`** on mutations that modify data — ensures the UI stays in sync\n4. **Use generated options/mutations** from \\`@/api/generated/@tanstack/react-query.gen\\` — never write \\`queryFn\\` manually\n5. **Use \\`select\\`** to reshape API responses at the hook level, not in components\n6. **Use \\`enabled\\`** for conditional queries (e.g. waiting for an ID from URL params)\n7. **Spread generated options first** (\\`...postsListOptions()\\`), then add overrides — this preserves the generated \\`queryKey\\` and \\`queryFn\\`\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const pageStructureSkill: Skill = {\n id: 'page-structure',\n name: 'Page & Route Structure',\n description: 'Page folders, feature modules, routing conventions, and route composition patterns.',\n\n render(_ctx: SkillContext): string {\n return `## Page & Route Structure\n\n> **RULE: Every page and feature owns its own \\`routes.tsx\\`. The central router only composes them — never import page components directly into \\`routes.tsx\\`.**\n\n### Standalone Pages (\\`src/pages/\\`)\n\nEach page gets its own folder:\n\n\\`\\`\\`\npages/<page>/\n├── <page>.tsx # Page component (default export)\n├── routes.tsx # Exports RouteObject[] for this page\n├── index.ts # Re-exports public members (routes)\n├── components/ # Components private to this page (optional)\n└── hooks/ # Page-local hooks (UI logic, not API hooks)\n\\`\\`\\`\n\n**\\`routes.tsx\\`** — defines the route config using the \\`Path\\` enum:\n\\`\\`\\`tsx\nimport type { RouteObject } from 'react-router-dom'\nimport { Path } from '@/router/paths'\nimport SettingsPage from './settings'\n\nexport const settingsRoutes: RouteObject[] = [\n { path: Path.Settings, element: <SettingsPage /> },\n]\n\\`\\`\\`\n\n**\\`index.ts\\`** — re-exports only public members:\n\\`\\`\\`ts\nexport { settingsRoutes } from './routes'\n\\`\\`\\`\n\n### Feature Pages (\\`src/features/\\`)\n\nFeatures that have pages include a \\`routes.tsx\\` at the feature root:\n\n\\`\\`\\`\nfeatures/<feature>/\n├── components/ # UI components scoped to this feature\n├── hooks/ # Custom hooks (queries, mutations, logic)\n├── pages/ # Page components (default exports)\n├── routes.tsx # RouteObject[] for all pages in this feature\n└── index.ts # Re-exports routes + public API\n\\`\\`\\`\n\n**\\`routes.tsx\\`** — groups related routes using the \\`Path\\` enum:\n\\`\\`\\`tsx\nimport { Outlet, type RouteObject } from 'react-router-dom'\nimport { Path } from '@/router/paths'\nimport PostsPage from './pages/posts-page'\nimport PostDetailPage from './pages/post-detail-page'\n\nexport const postsRoutes: RouteObject[] = [\n {\n path: Path.Posts,\n element: <Outlet />,\n children: [\n { index: true, element: <PostsPage /> },\n { path: ':id', element: <PostDetailPage /> },\n ],\n },\n]\n\\`\\`\\`\n\n**\\`index.ts\\`** — exports routes first:\n\\`\\`\\`ts\nexport { postsRoutes } from './routes'\nexport { usePosts, useCreatePost, useUpdatePost, useDeletePost } from '@/api/hooks/posts'\n\\`\\`\\`\n\n### Route Paths (\\`src/router/paths.ts\\`)\n\nAll route paths are defined in a central \\`Path\\` enum — **never use hardcoded path strings**:\n\n\\`\\`\\`ts\nexport enum Path {\n Home = '/',\n Login = '/login',\n Register = '/register',\n ForgotPassword = '/forgot-password',\n ResetPassword = '/reset-password/:token',\n Dashboard = '/dashboard',\n // blacksmith:path\n}\n\\`\\`\\`\n\nUse \\`Path\\` everywhere — in route definitions, \\`navigate()\\`, \\`<Link to={}\\`\\`, etc.:\n\\`\\`\\`tsx\nimport { Path } from '@/router/paths'\n\n// In routes\n{ path: Path.Dashboard, element: <DashboardPage /> }\n\n// In navigation\nnavigate(Path.Login)\n<Link to={Path.Home}>Home</Link>\n\\`\\`\\`\n\nFor dynamic paths, use the \\`buildPath\\` helper:\n\\`\\`\\`ts\nimport { Path, buildPath } from '@/router/paths'\n\nbuildPath(Path.ResetPassword, { token: 'abc123' })\n// => '/reset-password/abc123'\n\\`\\`\\`\n\nThe \\`Path\\` enum is re-exported from \\`@/router\\` along with \\`buildPath\\`.\n\n### Central Router (\\`src/router/routes.tsx\\`)\n\nThe central router imports and spreads route arrays — it never imports page components directly:\n\n\\`\\`\\`tsx\nimport { homeRoutes } from '@/pages/home'\nimport { dashboardRoutes } from '@/pages/dashboard'\nimport { authRoutes } from '@/features/auth'\nimport { postsRoutes } from '@/features/posts'\n// blacksmith:import\n\nconst publicRoutes: RouteObject[] = [\n ...homeRoutes,\n]\n\nconst privateRoutes: RouteObject[] = [\n ...dashboardRoutes,\n ...postsRoutes,\n // blacksmith:routes\n]\n\\`\\`\\`\n\n### Auto-Registration\n\n\\`blacksmith make:resource\\` automatically registers routes using marker comments:\n- \\`// blacksmith:path\\` — new \\`Path\\` enum entry is inserted above this marker in \\`paths.ts\\`\n- \\`// blacksmith:import\\` — new import line is inserted above this marker in \\`routes.tsx\\`\n- \\`// blacksmith:routes\\` — new spread line is inserted above this marker in \\`routes.tsx\\`\n\nNever remove these markers. They must stay in the \\`Path\\` enum, \\`privateRoutes\\` array, and import block.\n\n### When to Use Pages vs Features\n\n| Use \\`pages/<page>/\\` | Use \\`features/<feature>/\\` |\n|---|---|\n| Standalone pages (home, dashboard, settings) | CRUD resources with multiple pages |\n| No shared hooks or components | Has hooks, components, and pages that belong together |\n| Single route | Multiple related routes (list + detail + edit) |\n\n### Component Decomposition\n\n> **RULE: Pages are orchestrators, not monoliths. Break every page into small, focused child components stored in \\`components/\\`.**\n\nA page component should read data (via hooks), pass it down as props, and compose child components. It should contain minimal JSX itself.\n\n\\`\\`\\`\npages/dashboard/\n├── dashboard.tsx # Page: composes children, calls hooks\n├── components/\n│ ├── stats-cards.tsx # Renders the stats grid\n│ ├── recent-activity.tsx # Renders activity feed\n│ └── quick-actions.tsx # Renders action buttons\n├── hooks/\n│ └── use-dashboard-data.ts # All data fetching for this page\n├── routes.tsx\n└── index.ts\n\\`\\`\\`\n\n\\`\\`\\`tsx\n// dashboard.tsx — thin orchestrator using @blacksmith-ui/react layout\nimport { Stack, Grid, Divider } from '@blacksmith-ui/react'\nimport { StatsCards } from './components/stats-cards'\nimport { RecentActivity } from './components/recent-activity'\nimport { QuickActions } from './components/quick-actions'\nimport { useDashboardData } from './hooks/use-dashboard-data'\n\nexport default function DashboardPage() {\n const { stats, activity, isLoading } = useDashboardData()\n\n return (\n <Stack gap={6}>\n <StatsCards stats={stats} isLoading={isLoading} />\n <Divider />\n <Grid columns={{ base: 1, lg: 3 }} gap={6}>\n <RecentActivity items={activity} isLoading={isLoading} className=\"lg:col-span-2\" />\n <QuickActions />\n </Grid>\n </Stack>\n )\n}\n\\`\\`\\`\n\n**When to extract a child component:**\n- A section of JSX exceeds ~30 lines\n- A block has its own loading/error state\n- A block is logically independent (e.g. a table, a form, a sidebar)\n- A block could be reused on another page (move to \\`shared/\\` or the feature's \\`components/\\`)\n\n**When NOT to extract:**\n- A few lines of simple, static markup (headings, wrappers)\n- Extracting would just move props through another layer with no clarity gain\n\n### Separating Logic into Hooks\n\n> **RULE: Components render. Hooks think. Never mix data fetching, transformations, or complex state logic into component bodies.**\n\nExtract logic into hooks in the \\`hooks/\\` folder co-located with the page or feature:\n\n\\`\\`\\`tsx\n// BAD — logic mixed into the component\nexport default function OrdersPage() {\n const [page, setPage] = useState(1)\n const [search, setSearch] = useState('')\n const debouncedSearch = useDebounce(search, 300)\n const { data, isLoading } = useApiQuery({\n ...ordersListOptions({ query: { page, search: debouncedSearch } }),\n select: (d: any) => ({ orders: d.results ?? [], total: d.count ?? 0 }),\n })\n\n const deleteOrder = useApiMutation({\n ...ordersDestroyMutation(),\n invalidateKeys: [ordersListQueryKey()],\n })\n\n return ( /* 200 lines of JSX using all of the above */ )\n}\n\\`\\`\\`\n\n\\`\\`\\`tsx\n// GOOD — logic in a hook, component just renders\n// hooks/use-orders-page.ts\nexport function useOrdersPage() {\n const [page, setPage] = useState(1)\n const [search, setSearch] = useState('')\n const debouncedSearch = useDebounce(search, 300)\n\n const { data, isLoading, errorMessage } = useOrders({\n page,\n search: debouncedSearch,\n })\n\n const deleteOrder = useDeleteOrder()\n\n return { orders: data?.orders ?? [], total: data?.total ?? 0, isLoading, errorMessage, page, setPage, search, setSearch, deleteOrder }\n}\n\n// orders-page.tsx\nimport { Stack } from '@blacksmith-ui/react'\nimport { useOrdersPage } from './hooks/use-orders-page'\nimport { OrdersTable } from './components/orders-table'\nimport { OrdersToolbar } from './components/orders-toolbar'\n\nexport default function OrdersPage() {\n const { orders, total, isLoading, page, setPage, search, setSearch, deleteOrder } = useOrdersPage()\n\n return (\n <Stack gap={4}>\n <OrdersToolbar search={search} onSearchChange={setSearch} />\n <OrdersTable orders={orders} isLoading={isLoading} onDelete={(id) => deleteOrder.mutate({ path: { id } })} />\n </Stack>\n )\n}\n\\`\\`\\`\n\n**What goes into a hook:**\n- API queries and mutations\n- Derived/computed state\n- Debouncing, pagination, filtering logic\n- Form setup (\\`useForm\\`, schema, submit handler)\n- Any \\`useEffect\\` or \\`useState\\` beyond a simple UI toggle\n\n**What stays in the component:**\n- Simple UI toggles (\\`useState\\` for a modal open/close is fine inline)\n- JSX composition and prop passing\n- Event handler wiring (calling \\`hook.mutate()\\`, \\`navigate()\\`, etc.)\n\n### Key Rules\n\n1. **Every page/feature owns its routes** — the route config lives next to the page, not in the central router\n2. **Use the \\`Path\\` enum for all route paths** — never hardcode path strings; import \\`Path\\` from \\`@/router/paths\\`\n3. **\\`index.ts\\` is the public API** — only export what other modules need (routes, hooks, components)\n4. **Page components use default exports** — this is the one exception to the named-export convention\n5. **Routes use named exports** — \\`export const settingsRoutes\\` not \\`export default\\`\n6. **Private components/hooks stay in the page folder** — if only one page uses it, co-locate it there\n7. **Never import across page folders** — if something is shared, move it to \\`shared/\\` or a feature\n8. **Keep marker comments intact** — \\`// blacksmith:path\\`, \\`// blacksmith:import\\`, and \\`// blacksmith:routes\\` are required for auto-registration\n9. **Pages are orchestrators** — break pages into child components in \\`components/\\`, extract logic into hooks in \\`hooks/\\`\n10. **Components render, hooks think** — never put data fetching, transformations, or complex state directly in component bodies\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const blacksmithUiReactSkill: Skill = {\n id: 'blacksmith-ui-react',\n name: '@blacksmith-ui/react',\n description: 'Core UI component library — 60+ components for layout, typography, inputs, data display, overlays, feedback, media, and navigation.',\n\n render(_ctx: SkillContext): string {\n return `## @blacksmith-ui/react — Core UI Components (60+)\n\n> **CRITICAL RULE: Every UI element MUST be built using \\`@blacksmith-ui/react\\` components — including layout and typography.**\n> Do NOT use raw HTML elements when a Blacksmith-UI component exists for that purpose.\n> This includes layout: use \\`Flex\\`, \\`Stack\\`, \\`Grid\\`, \\`Box\\`, \\`Container\\` instead of \\`<div>\\` with flex/grid classes.\n> This includes typography: use \\`Text\\` and \\`Typography\\` instead of raw \\`<h1>\\`–\\`<h6>\\`, \\`<p>\\`, \\`<span>\\`.\n\n### Layout\n\n| Component | Use instead of | Description |\n|-----------|---------------|-------------|\n| \\`Box\\` | \\`<div>\\` | Base layout primitive with style props |\n| \\`Flex\\` | \\`<div className=\"flex ...\">\\` | Flexbox container with style props (\\`direction\\`, \\`align\\`, \\`justify\\`, \\`gap\\`, \\`wrap\\`) |\n| \\`Grid\\` | \\`<div className=\"grid ...\">\\` | CSS Grid container (\\`columns\\`, \\`rows\\`, \\`gap\\`) |\n| \\`Stack\\` | \\`<div className=\"flex flex-col gap-...\">\\` | Vertical/horizontal stack (\\`direction\\`, \\`gap\\`) |\n| \\`Container\\` | \\`<div className=\"max-w-7xl mx-auto px-...\">\\` | Max-width centered container |\n| \\`Divider\\` | \\`<hr>\\` or border hacks | Visual separator (horizontal/vertical) |\n| \\`AspectRatio\\` | padding-bottom trick | Maintain aspect ratio for content |\n| \\`Resizable\\` | custom resize logic | Resizable panel groups |\n| \\`ScrollArea\\` | \\`overflow-auto\\` divs | Custom scrollbar container |\n\n### Typography\n\n| Component | Use instead of | Description |\n|-----------|---------------|-------------|\n| \\`Text\\` | \\`<p>\\`, \\`<span>\\` | Text display with style props (\\`size\\`, \\`weight\\`, \\`color\\`, \\`align\\`) |\n| \\`Typography\\` | \\`<h1>\\`–\\`<h6>\\`, \\`<p>\\` | Semantic heading/paragraph elements (\\`variant\\`: h1–h6, p, lead, muted, etc.) |\n| \\`Label\\` | \\`<label>\\` | Form label with accessibility support |\n\n### Cards & Containers\n\n- \\`Card\\`, \\`CardHeader\\`, \\`CardTitle\\`, \\`CardDescription\\`, \\`CardContent\\`, \\`CardFooter\\` — Use instead of styled \\`<div>\\` containers\n- \\`StatCard\\` — Use for metric/stat display (value, label, trend)\n- \\`EmptyState\\` — Use for empty content placeholders instead of custom empty divs\n\n### Actions\n\n- \\`Button\\` — Use instead of \\`<button>\\` or \\`<a>\\` styled as buttons\n - Variants: \\`default\\`, \\`secondary\\`, \\`destructive\\`, \\`outline\\`, \\`ghost\\`, \\`link\\`\n - Sizes: \\`sm\\`, \\`default\\`, \\`lg\\`, \\`icon\\`\n- \\`Toggle\\`, \\`ToggleGroup\\` — Use for toggle buttons\n- \\`DropdownMenu\\`, \\`DropdownMenuTrigger\\`, \\`DropdownMenuContent\\`, \\`DropdownMenuItem\\`, \\`DropdownMenuSeparator\\`, \\`DropdownMenuLabel\\` — Use for action menus\n- \\`ContextMenu\\` — Use for right-click menus\n- \\`Menubar\\` — Use for application menu bars\n- \\`AlertDialog\\`, \\`AlertDialogTrigger\\`, \\`AlertDialogContent\\`, \\`AlertDialogAction\\`, \\`AlertDialogCancel\\` — Use for destructive action confirmations\n\n### Data Entry\n\n- \\`Input\\` — Use instead of \\`<input>\\`\n- \\`SearchInput\\` — Use for search fields (has built-in search icon)\n- \\`Textarea\\` — Use instead of \\`<textarea>\\`\n- \\`NumberInput\\` — Use for numeric inputs with increment/decrement\n- \\`Select\\`, \\`SelectTrigger\\`, \\`SelectContent\\`, \\`SelectItem\\`, \\`SelectValue\\` — Use instead of \\`<select>\\`\n- \\`Checkbox\\` — Use instead of \\`<input type=\"checkbox\">\\`\n- \\`RadioGroup\\`, \\`RadioGroupItem\\` — Use instead of \\`<input type=\"radio\">\\`\n- \\`Switch\\` — Use for toggle switches\n- \\`Slider\\` — Use for single range inputs\n- \\`RangeSlider\\` — Use for dual-handle range selection\n- \\`DatePicker\\` — Use for date selection with calendar popup\n- \\`PinInput\\` / \\`InputOTP\\` — Use for PIN/OTP code entry\n- \\`ColorPicker\\` — Use for color selection\n- \\`FileUpload\\` — Use for file upload with drag & drop\n- \\`TagInput\\` — Use for tag/chip input with add/remove\n- \\`Rating\\` — Use for star/icon rating selection\n- \\`Label\\` — Use instead of \\`<label>\\`\n\n### Data Display\n\n- \\`Table\\`, \\`TableHeader\\`, \\`TableBody\\`, \\`TableRow\\`, \\`TableHead\\`, \\`TableCell\\` — Use instead of \\`<table>\\` elements\n- \\`DataTable\\` — Use for feature-rich tables with sorting, filtering, and pagination\n- \\`Badge\\` — Use for status indicators, tags, counts (variants: \\`default\\`, \\`secondary\\`, \\`destructive\\`, \\`outline\\`)\n- \\`Avatar\\`, \\`AvatarImage\\`, \\`AvatarFallback\\` — Use for user profile images\n- \\`Tooltip\\`, \\`TooltipTrigger\\`, \\`TooltipContent\\`, \\`TooltipProvider\\` — Use for hover hints\n- \\`HoverCard\\` — Use for rich hover content\n- \\`Calendar\\` — Use for full calendar display\n- \\`Chart\\` — Use for data visualization (powered by Recharts)\n- \\`Timeline\\` — Use for chronological event display\n- \\`Tree\\` — Use for hierarchical tree views\n- \\`List\\` — Use for structured list display instead of \\`<ul>\\`/\\`<ol>\\`\n- \\`Skeleton\\` — Use for loading placeholders\n- \\`Spinner\\` — Use for loading indicators\n- \\`Progress\\` — Use for progress bars\n- \\`Pagination\\`, \\`PaginationContent\\`, \\`PaginationItem\\`, \\`PaginationLink\\`, \\`PaginationNext\\`, \\`PaginationPrevious\\` — Use for paginated lists\n\n### Tabs & Accordion\n\n- \\`Tabs\\`, \\`TabsList\\`, \\`TabsTrigger\\`, \\`TabsContent\\` — Use for tabbed interfaces\n- \\`Accordion\\`, \\`AccordionItem\\`, \\`AccordionTrigger\\`, \\`AccordionContent\\` — Use for collapsible sections\n\n### Overlays\n\n- \\`Dialog\\`, \\`DialogTrigger\\`, \\`DialogContent\\`, \\`DialogHeader\\`, \\`DialogTitle\\`, \\`DialogDescription\\`, \\`DialogFooter\\` — Use for modals\n- \\`AlertDialog\\` — Use for confirmation dialogs\n- \\`Drawer\\` / \\`Sheet\\`, \\`SheetTrigger\\`, \\`SheetContent\\`, \\`SheetHeader\\`, \\`SheetTitle\\`, \\`SheetDescription\\` — Use for slide-out panels\n- \\`Popover\\` — Use for floating content panels\n- \\`CommandPalette\\` — Use for searchable command menus (cmdk-based)\n\n### Navigation\n\n- \\`Breadcrumb\\`, \\`BreadcrumbList\\`, \\`BreadcrumbItem\\`, \\`BreadcrumbLink\\`, \\`BreadcrumbSeparator\\` — Use for breadcrumb trails\n- \\`NavigationMenu\\`, \\`NavigationMenuList\\`, \\`NavigationMenuItem\\`, \\`NavigationMenuTrigger\\`, \\`NavigationMenuContent\\` — Use for site navigation\n- \\`Sidebar\\` — Use for app sidebars\n- \\`Dock\\` — Use for macOS-style dock navigation\n- \\`BackToTop\\` — Use for scroll-to-top buttons\n\n### Feedback\n\n- \\`Alert\\`, \\`AlertTitle\\`, \\`AlertDescription\\` — Use for inline messages/warnings\n- \\`AlertBanner\\` — Use for full-width alert banners\n- \\`Toast\\` / \\`Toaster\\` / \\`useToast\\` — Use for transient notifications\n- \\`SonnerToaster\\` — Sonner-based toast notifications\n\n### Media\n\n- \\`Image\\` — Use instead of \\`<img>\\` for optimized image display\n- \\`VideoPlayer\\` — Use for video playback\n- \\`CodeBlock\\` — Use for syntax-highlighted code (Shiki-powered)\n- \\`Carousel\\` — Use for image/content carousels\n- \\`Lightbox\\` — Use for full-screen media viewers\n\n### Specialized\n\n- \\`Stepper\\` / \\`Wizard\\` — Use for multi-step workflows\n- \\`NotificationCenter\\` / \\`useNotificationCenter\\` — Use for notification management\n- \\`SpotlightTour\\` — Use for guided feature tours\n\n### Utilities & Hooks\n\n- \\`cn()\\` — Merge class names (clsx + tailwind-merge)\n- \\`useToast()\\` — Programmatic toast notifications\n- \\`useMobile()\\` — Responsive breakpoint detection\n- \\`useDarkMode()\\` — Dark mode toggle. Returns \\`{ isDark, toggle }\\`\n\n---\n\n### Component-First Rules\n\n1. **Layout**: NEVER use \\`<div className=\"flex ...\">\\` or \\`<div className=\"grid ...\">\\`. Use \\`<Flex>\\`, \\`<Grid>\\`, \\`<Stack>\\`, \\`<Box>\\` from \\`@blacksmith-ui/react\\`.\n2. **Centering/max-width**: NEVER use \\`<div className=\"max-w-7xl mx-auto px-...\">\\`. Use \\`<Container>\\`.\n3. **Typography**: NEVER use raw \\`<h1>\\`–\\`<h6>\\` or \\`<p>\\` with Tailwind text classes. Use \\`<Typography variant=\"h2\">\\` or \\`<Text>\\`.\n4. **Separators**: NEVER use \\`<hr>\\` or border hacks. Use \\`<Divider>\\`.\n5. **Images**: NEVER use raw \\`<img>\\`. Use \\`<Image>\\` from \\`@blacksmith-ui/react\\` (use \\`Avatar\\` for profile pictures).\n6. **Lists**: NEVER use \\`<ul>\\`/\\`<ol>\\` for structured display lists. Use \\`<List>\\` from \\`@blacksmith-ui/react\\`. Plain \\`<ul>\\`/\\`<ol>\\` is only acceptable for simple inline content lists.\n7. **Buttons**: NEVER use \\`<button>\\` or \\`<a>\\` styled as a button. Use \\`<Button>\\`.\n8. **Inputs**: NEVER use \\`<input>\\`, \\`<textarea>\\`, \\`<select>\\` directly. Use the Blacksmith-UI equivalents.\n9. **Cards**: NEVER use a styled \\`<div>\\` as a card. Use \\`Card\\` + sub-components.\n10. **Tables**: NEVER use raw \\`<table>\\` HTML. Use \\`Table\\` or \\`DataTable\\`.\n11. **Loading**: NEVER use custom \\`animate-pulse\\` divs. Use \\`Skeleton\\` or \\`Spinner\\`.\n12. **Modals**: NEVER build custom modals. Use \\`Dialog\\`, \\`AlertDialog\\`, \\`Drawer\\`, or \\`Sheet\\`.\n13. **Feedback**: NEVER use plain styled text for errors/warnings. Use \\`Alert\\` or \\`useToast\\`.\n14. **Empty states**: NEVER build custom empty-state UIs. Use \\`EmptyState\\`.\n15. **Metrics**: NEVER build custom stat/metric cards. Use \\`StatCard\\`.\n\n### When Raw HTML IS Acceptable\n\n- \\`<main>\\`, \\`<section>\\`, \\`<header>\\`, \\`<footer>\\`, \\`<nav>\\`, \\`<article>\\`, \\`<aside>\\` — semantic HTML landmarks for page structure (but use \\`Flex\\`/\\`Stack\\`/\\`Grid\\` inside them for layout)\n- \\`<Link>\\` from react-router-dom — for page navigation (use \\`<Button asChild><Link>...</Link></Button>\\` if it needs button styling)\n- Icon components from \\`lucide-react\\`\n- \\`<form>\\` element when used with React Hook Form (but use \\`@blacksmith-ui/forms\\` components inside)\n\n### Design Tokens & Theming\n\n- \\`ThemeProvider\\` — Wrap app to apply preset or custom theme\n- Built-in presets: \\`default\\`, \\`blue\\`, \\`green\\`, \\`violet\\`, \\`red\\`, \\`neutral\\`\n- All components use HSL CSS variables (\\`--background\\`, \\`--foreground\\`, \\`--primary\\`, etc.)\n- Dark mode: \\`.dark\\` class strategy on \\`<html>\\`, or \\`<ThemeProvider mode=\"dark\">\\`\n- Border radius: controlled by \\`--radius\\` CSS variable\n- Extend with \\`className\\` prop + \\`cn()\\` utility for custom styles\n- Global styles: \\`@import '@blacksmith-ui/react/styles.css'\\` in app entry\n\n### Example: HowItWorks Section (Correct Way)\n\n\\`\\`\\`tsx\nimport { Container, Stack, Flex, Grid, Text, Typography, Image } from '@blacksmith-ui/react'\nimport { howItWorksSteps } from '../data'\n\nexport function HowItWorks() {\n return (\n <Box as=\"section\" className=\"py-16 sm:py-20\">\n <Container>\n <Stack gap={3} align=\"center\" className=\"mb-12\">\n <Typography variant=\"h2\">How It Works</Typography>\n <Text color=\"muted\">Book your stay in three simple steps</Text>\n </Stack>\n\n <Grid columns={{ base: 1, md: 3 }} gap={8} className=\"max-w-4xl mx-auto\">\n {howItWorksSteps.map((item) => (\n <Stack key={item.step} align=\"center\" gap={4}>\n <Box className=\"relative\">\n <Flex align=\"center\" justify=\"center\" className=\"h-16 w-16 rounded-full bg-primary text-primary-foreground shadow-lg shadow-primary/30\">\n <item.icon className=\"h-7 w-7\" />\n </Flex>\n <Flex align=\"center\" justify=\"center\" className=\"absolute -top-1 -right-1 h-6 w-6 rounded-full bg-background border-2 border-primary\">\n <Text size=\"xs\" weight=\"bold\" color=\"primary\">{item.step}</Text>\n </Flex>\n </Box>\n <Stack gap={2} align=\"center\">\n <Text size=\"lg\" weight=\"bold\">{item.title}</Text>\n <Text size=\"sm\" color=\"muted\" align=\"center\" className=\"max-w-xs\">\n {item.description}\n </Text>\n </Stack>\n </Stack>\n ))}\n </Grid>\n </Container>\n </Box>\n )\n}\n\\`\\`\\`\n\n### Example: Resource List Page (Correct Way)\n\n\\`\\`\\`tsx\nimport {\n Stack, Flex,\n Card, CardHeader, CardTitle, CardContent,\n Button, Badge, Skeleton,\n Table, TableHeader, TableBody, TableRow, TableHead, TableCell,\n DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem,\n AlertDialog, AlertDialogTrigger, AlertDialogContent,\n AlertDialogAction, AlertDialogCancel,\n} from '@blacksmith-ui/react'\nimport { MoreHorizontal, Plus, Trash2, Edit } from 'lucide-react'\nimport { Link } from 'react-router-dom'\n\nfunction ResourceListPage({ resources, isLoading, onDelete }) {\n if (isLoading) {\n return (\n <Card>\n <CardContent className=\"p-6\">\n <Stack gap={4}>\n {Array.from({ length: 5 }).map((_, i) => (\n <Skeleton key={i} className=\"h-12 w-full\" />\n ))}\n </Stack>\n </CardContent>\n </Card>\n )\n }\n\n return (\n <Card>\n <CardHeader>\n <Flex align=\"center\" justify=\"between\">\n <CardTitle>Resources</CardTitle>\n <Button asChild>\n <Link to=\"/resources/new\"><Plus className=\"mr-2 h-4 w-4\" /> Create</Link>\n </Button>\n </Flex>\n </CardHeader>\n <CardContent>\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Title</TableHead>\n <TableHead>Status</TableHead>\n <TableHead className=\"w-12\" />\n </TableRow>\n </TableHeader>\n <TableBody>\n {resources.map((r) => (\n <TableRow key={r.id}>\n <TableCell>{r.title}</TableCell>\n <TableCell><Badge variant=\"outline\">{r.status}</Badge></TableCell>\n <TableCell>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\">\n <MoreHorizontal className=\"h-4 w-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent>\n <DropdownMenuItem asChild>\n <Link to={\\`/resources/\\${r.id}/edit\\`}>\n <Edit className=\"mr-2 h-4 w-4\" /> Edit\n </Link>\n </DropdownMenuItem>\n <AlertDialog>\n <AlertDialogTrigger asChild>\n <DropdownMenuItem onSelect={(e) => e.preventDefault()}>\n <Trash2 className=\"mr-2 h-4 w-4\" /> Delete\n </DropdownMenuItem>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogAction onClick={() => onDelete(r.id)}>\n Delete\n </AlertDialogAction>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n </AlertDialogContent>\n </AlertDialog>\n </DropdownMenuContent>\n </DropdownMenu>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </CardContent>\n </Card>\n )\n}\n\\`\\`\\`\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const blacksmithUiFormsSkill: Skill = {\n id: 'blacksmith-ui-forms',\n name: '@blacksmith-ui/forms',\n description: 'Form components using React Hook Form + Zod for validation and submission.',\n\n render(_ctx: SkillContext): string {\n return `## @blacksmith-ui/forms — Form Components (React Hook Form + Zod)\n\n> **RULE: ALWAYS use these for forms.** Do NOT build forms with raw \\`<form>\\`, \\`<input>\\`, \\`<label>\\`, or manual error display.\n\n\\`\\`\\`tsx\nimport { Form, FormField, FormInput, FormTextarea, FormSelect } from '@blacksmith-ui/forms'\n\\`\\`\\`\n\n### Components\n\n- \\`Form\\` — Wraps the entire form. Props: \\`form\\` (useForm instance), \\`onSubmit\\`\n- \\`FormField\\` — Wraps each field. Props: \\`name\\`, \\`label\\`, \\`description?\\`\n- \\`FormInput\\` — Text input within FormField. Props: \\`type\\`, \\`placeholder\\`\n- \\`FormTextarea\\` — Textarea within FormField. Props: \\`rows\\`, \\`placeholder\\`\n- \\`FormSelect\\` — Select within FormField. Props: \\`options\\`, \\`placeholder\\`\n- \\`FormCheckbox\\` — Checkbox within FormField\n- \\`FormSwitch\\` — Toggle switch within FormField\n- \\`FormRadioGroup\\` — Radio group within FormField. Props: \\`options\\`\n- \\`FormDatePicker\\` — Date picker within FormField\n- \\`FormError\\` — Displays field-level validation error (auto-handled by FormField)\n- \\`FormDescription\\` — Displays helper text below a field\n\n### Rules\n- NEVER use raw \\`<form>\\` with manual \\`<label>\\` and error \\`<p>\\` tags. Always use \\`Form\\` + \\`FormField\\`.\n- NEVER use \\`<input>\\`, \\`<textarea>\\`, \\`<select>\\` inside forms. Use \\`FormInput\\`, \\`FormTextarea\\`, \\`FormSelect\\`.\n\n### Form Pattern — ALWAYS follow this:\n\\`\\`\\`tsx\nimport { Form, FormField, FormInput, FormTextarea, FormSelect } from '@blacksmith-ui/forms'\nimport { Button } from '@blacksmith-ui/react'\nimport { useForm } from 'react-hook-form'\nimport { zodResolver } from '@hookform/resolvers/zod'\nimport { z } from 'zod'\n\nconst schema = z.object({\n title: z.string().min(1, 'Title is required'),\n description: z.string().optional(),\n status: z.enum(['draft', 'published']),\n})\n\ntype FormData = z.infer<typeof schema>\n\nfunction ResourceForm({ defaultValues, onSubmit, isSubmitting }: Props) {\n const form = useForm<FormData>({\n resolver: zodResolver(schema),\n defaultValues: { title: '', description: '', status: 'draft', ...defaultValues },\n })\n\n return (\n <Form form={form} onSubmit={onSubmit}>\n <FormField name=\"title\" label=\"Title\">\n <FormInput placeholder=\"Enter title\" />\n </FormField>\n <FormField name=\"description\" label=\"Description\">\n <FormTextarea rows={4} placeholder=\"Enter description\" />\n </FormField>\n <FormField name=\"status\" label=\"Status\">\n <FormSelect options={[\n { label: 'Draft', value: 'draft' },\n { label: 'Published', value: 'published' },\n ]} />\n </FormField>\n <Button type=\"submit\" disabled={isSubmitting}>\n {isSubmitting ? 'Saving...' : 'Save'}\n </Button>\n </Form>\n )\n}\n\\`\\`\\`\n\n### Example: Detail Page with Edit Dialog\n\\`\\`\\`tsx\nimport {\n Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter,\n Button, Badge, Separator,\n Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogFooter,\n Alert, AlertTitle, AlertDescription,\n} from '@blacksmith-ui/react'\nimport { Form, FormField, FormInput, FormTextarea } from '@blacksmith-ui/forms'\nimport { useForm } from 'react-hook-form'\nimport { zodResolver } from '@hookform/resolvers/zod'\nimport { z } from 'zod'\nimport { Edit, ArrowLeft } from 'lucide-react'\nimport { Link } from 'react-router-dom'\n\nconst editSchema = z.object({\n title: z.string().min(1, 'Required'),\n description: z.string().optional(),\n})\n\nfunction ResourceDetailPage({ resource, onUpdate, error }) {\n const form = useForm({\n resolver: zodResolver(editSchema),\n defaultValues: { title: resource.title, description: resource.description },\n })\n\n return (\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <div>\n <CardTitle>{resource.title}</CardTitle>\n <CardDescription>Created {new Date(resource.created_at).toLocaleDateString()}</CardDescription>\n </div>\n <div className=\"flex gap-2\">\n <Button variant=\"outline\" asChild>\n <Link to=\"/resources\"><ArrowLeft className=\"mr-2 h-4 w-4\" /> Back</Link>\n </Button>\n <Dialog>\n <DialogTrigger asChild>\n <Button><Edit className=\"mr-2 h-4 w-4\" /> Edit</Button>\n </DialogTrigger>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>Edit Resource</DialogTitle>\n </DialogHeader>\n <Form form={form} onSubmit={onUpdate}>\n <FormField name=\"title\" label=\"Title\">\n <FormInput />\n </FormField>\n <FormField name=\"description\" label=\"Description\">\n <FormTextarea rows={4} />\n </FormField>\n <DialogFooter>\n <Button type=\"submit\">Save Changes</Button>\n </DialogFooter>\n </Form>\n </DialogContent>\n </Dialog>\n </div>\n </div>\n </CardHeader>\n <Separator />\n <CardContent className=\"pt-6\">\n {error && (\n <Alert variant=\"destructive\" className=\"mb-4\">\n <AlertTitle>Error</AlertTitle>\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n )}\n <p>{resource.description || 'No description provided.'}</p>\n </CardContent>\n <CardFooter>\n <Badge>{resource.status}</Badge>\n </CardFooter>\n </Card>\n )\n}\n\\`\\`\\`\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const blacksmithUiAuthSkill: Skill = {\n id: 'blacksmith-ui-auth',\n name: '@blacksmith-ui/auth',\n description: 'Authentication UI components and hooks for login, registration, and password reset.',\n\n render(_ctx: SkillContext): string {\n return `## @blacksmith-ui/auth — Authentication UI\n\n> **RULE: ALWAYS use these for auth pages.** Do NOT build custom login/register forms.\n\n\\`\\`\\`tsx\nimport { AuthProvider, LoginForm, RegisterForm, useAuth } from '@blacksmith-ui/auth'\n\\`\\`\\`\n\n### Components\n\n- \\`AuthProvider\\` — Context provider wrapping the app. Props: \\`config: { adapter, socialProviders? }\\`\n- \\`LoginForm\\` — Complete login form with email/password fields, validation, and links\n - Props: \\`onSubmit: (data: { email, password }) => void\\`, \\`onRegisterClick\\`, \\`onForgotPasswordClick\\`, \\`error\\`, \\`loading\\`\n- \\`RegisterForm\\` — Registration form with email, password, and display name\n - Props: \\`onSubmit: (data: { email, password, displayName }) => void\\`, \\`onLoginClick\\`, \\`error\\`, \\`loading\\`\n- \\`ForgotPasswordForm\\` — Password reset email request\n - Props: \\`onSubmit: (data: { email }) => void\\`, \\`onLoginClick\\`, \\`error\\`, \\`loading\\`\n- \\`ResetPasswordForm\\` — Set new password form\n - Props: \\`onSubmit: (data: { password, code }) => void\\`, \\`code\\`, \\`onLoginClick\\`, \\`error\\`, \\`loading\\`\n\n### Hooks\n\n- \\`useAuth\\` — Hook for auth state and actions\n - Returns: \\`user\\`, \\`loading\\`, \\`error\\`, \\`signInWithEmail(email, password)\\`, \\`signUpWithEmail(email, password, displayName?)\\`, \\`signOut()\\`, \\`sendPasswordResetEmail(email)\\`, \\`confirmPasswordReset(code, newPassword)\\`, \\`socialProviders\\`\n\n### Adapter\n\n- \\`AuthAdapter\\` — Interface for custom auth backends (Django JWT adapter already configured in \\`frontend/src/features/auth/adapter.ts\\`)\n\n### Rules\n- NEVER build custom login/register forms. Use \\`LoginForm\\`, \\`RegisterForm\\`, etc. from \\`@blacksmith-ui/auth\\`.\n- NEVER manage auth state manually. Use \\`useAuth\\` hook.\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const blacksmithHooksSkill: Skill = {\n id: 'blacksmith-hooks',\n name: '@blacksmith-ui/hooks',\n description: '74 production-ready React hooks for state, DOM, timers, async, browser APIs, and layout.',\n\n render(_ctx: SkillContext): string {\n return `## @blacksmith-ui/hooks — React Hooks Library\n\nA collection of 74 production-ready React hooks. SSR-safe, fully typed, zero dependencies, tree-shakeable.\n\n> **RULE: Use \\`@blacksmith-ui/hooks\\` instead of writing custom hooks when one exists for that purpose.**\n> Before creating a new hook, check if one already exists below.\n\n\\`\\`\\`tsx\nimport { useToggle, useLocalStorage, useDebounce, useClickOutside } from '@blacksmith-ui/hooks'\n\\`\\`\\`\n\n### State & Data\n\n| Hook | Description |\n|------|-------------|\n| \\`useToggle\\` | Boolean state with \\`toggle\\`, \\`on\\`, \\`off\\` actions |\n| \\`useDisclosure\\` | Open/close/toggle state for modals, drawers, etc. |\n| \\`useCounter\\` | Numeric counter with optional min/max clamping |\n| \\`useList\\` | Array state with push, remove, update, insert, filter, clear |\n| \\`useMap\\` | Map state with set, remove, clear helpers |\n| \\`useSet\\` | Set state with add, remove, toggle, has, clear helpers |\n| \\`useHistoryState\\` | State with undo/redo history |\n| \\`useDefault\\` | State that falls back to a default when set to null/undefined |\n| \\`useQueue\\` | FIFO queue data structure |\n| \\`useStack\\` | LIFO stack data structure |\n| \\`useLocalStorage\\` | Persist state to localStorage with JSON serialization |\n| \\`useSessionStorage\\` | Persist state to sessionStorage with JSON serialization |\n| \\`useUncontrolled\\` | Controlled/uncontrolled component pattern helper |\n\n### Values & Memoization\n\n| Hook | Description |\n|------|-------------|\n| \\`useDebounce\\` | Debounce a value with configurable delay |\n| \\`useDebouncedCallback\\` | Debounce a callback function |\n| \\`useThrottle\\` | Throttle a value with configurable interval |\n| \\`useThrottledCallback\\` | Throttle a callback function |\n| \\`usePrevious\\` | Track the previous value of a variable |\n| \\`useLatest\\` | Ref that always points to the latest value |\n| \\`useConst\\` | Compute a value once and return it on every render |\n| \\`useSyncedRef\\` | Keep a ref synchronized with the latest value |\n\n### DOM & Browser\n\n| Hook | Description |\n|------|-------------|\n| \\`useClickOutside\\` | Detect clicks outside a ref element |\n| \\`useEventListener\\` | Attach event listeners to window or elements |\n| \\`useElementSize\\` | Track element width/height via ResizeObserver |\n| \\`useHover\\` | Track mouse hover state |\n| \\`useKeyPress\\` | Listen for a specific key press |\n| \\`useKeyCombo\\` | Listen for key + modifier combinations |\n| \\`useLongPress\\` | Detect long press gestures |\n| \\`useFullscreen\\` | Manage the Fullscreen API |\n| \\`useTextSelection\\` | Track currently selected text |\n| \\`useFocusWithin\\` | Track whether focus is inside a container |\n| \\`useFocusTrap\\` | Trap Tab/Shift+Tab focus within a container |\n| \\`useBoundingClientRect\\` | Track element bounding rect via ResizeObserver |\n| \\`useSwipe\\` | Detect touch swipe direction |\n| \\`useDrag\\` | Track mouse drag with position and delta |\n| \\`useElementVisibility\\` | Check if an element is in the viewport |\n| \\`useScrollPosition\\` | Track window scroll position |\n| \\`useScrollLock\\` | Lock/unlock body scroll |\n| \\`useMutationObserver\\` | Observe DOM mutations |\n| \\`useIntersectionObserver\\` | Observe element intersection with viewport |\n\n### Timers & Lifecycle\n\n| Hook | Description |\n|------|-------------|\n| \\`useInterval\\` | setInterval wrapper with pause support |\n| \\`useTimeout\\` | setTimeout wrapper with manual clear |\n| \\`useCountdown\\` | Countdown timer with start/pause/reset |\n| \\`useStopwatch\\` | Stopwatch with lap support |\n| \\`useIdleTimer\\` | Detect user idle time |\n| \\`useUpdateEffect\\` | useEffect that skips the initial render |\n| \\`useIsomorphicLayoutEffect\\` | SSR-safe useLayoutEffect |\n| \\`useIsMounted\\` | Check if component is currently mounted |\n| \\`useIsFirstRender\\` | Check if this is the first render |\n\n### Async & Network\n\n| Hook | Description |\n|------|-------------|\n| \\`useFetch\\` | Declarative data fetching with loading/error states (use for external URLs; use TanStack Query for API calls) |\n| \\`useAsync\\` | Execute async functions with status tracking |\n| \\`useScript\\` | Dynamically load external scripts |\n| \\`useWebSocket\\` | WebSocket connection with auto-reconnect |\n| \\`useSSE\\` | Server-Sent Events (EventSource) wrapper |\n| \\`usePolling\\` | Poll an async function at a fixed interval |\n| \\`useAbortController\\` | Manage AbortController lifecycle |\n| \\`useRetry\\` | Retry async operations with exponential backoff |\n| \\`useSearch\\` | Filter arrays with debounced search |\n\n### Browser APIs\n\n| Hook | Description |\n|------|-------------|\n| \\`useMediaQuery\\` | Reactive CSS media query matching |\n| \\`useColorScheme\\` | Detect system color scheme preference |\n| \\`useCopyToClipboard\\` | Copy text to clipboard with status feedback |\n| \\`useOnline\\` | Track network connectivity |\n| \\`useWindowSize\\` | Track window dimensions |\n| \\`usePageVisibility\\` | Detect page visibility state |\n| \\`usePageLeave\\` | Detect when the user leaves the page |\n| \\`useFavicon\\` | Dynamically change the favicon |\n| \\`useReducedMotion\\` | Respect prefers-reduced-motion |\n| \\`useBreakpoint\\` | Responsive breakpoint detection |\n| \\`useIsClient\\` | SSR-safe client-side detection |\n\n### Layout & UI\n\n| Hook | Description |\n|------|-------------|\n| \\`useStickyHeader\\` | Detect when header should be sticky |\n| \\`useVirtualList\\` | Virtualized list rendering for large datasets |\n| \\`useInfiniteScroll\\` | Infinite scroll with threshold detection |\n| \\`useCollapse\\` | Collapse/expand animation with prop getters |\n| \\`useSteps\\` | Multi-step flow navigation |\n\n### Common Patterns\n\n**Modal with click-outside dismiss:**\n\\`\\`\\`tsx\nimport { useDisclosure, useClickOutside } from '@blacksmith-ui/hooks'\n\nfunction MyComponent() {\n const [opened, { open, close }] = useDisclosure(false)\n const ref = useClickOutside<HTMLDivElement>(close)\n\n return (\n <>\n <Button onClick={open}>Open</Button>\n {opened && <div ref={ref}>Modal content</div>}\n </>\n )\n}\n\\`\\`\\`\n\n**Debounced search:**\n\\`\\`\\`tsx\nimport { useDebounce, useSearch } from '@blacksmith-ui/hooks'\n\nfunction SearchPage({ items }) {\n const [query, setQuery] = useState('')\n const debouncedQuery = useDebounce(query, 300)\n const results = useSearch(items, debouncedQuery, ['title', 'description'])\n\n return (\n <>\n <Input value={query} onChange={(e) => setQuery(e.target.value)} />\n {results.map(item => <div key={item.id}>{item.title}</div>)}\n </>\n )\n}\n\\`\\`\\`\n\n**Persisted state with undo:**\n\\`\\`\\`tsx\nimport { useLocalStorage, useHistoryState } from '@blacksmith-ui/hooks'\n\nfunction Editor() {\n const [saved, setSaved] = useLocalStorage('draft', '')\n const [content, { set, undo, redo, canUndo, canRedo }] = useHistoryState(saved)\n\n const handleSave = () => setSaved(content)\n}\n\\`\\`\\`\n\n**Responsive layout:**\n\\`\\`\\`tsx\nimport { useBreakpoint, useWindowSize } from '@blacksmith-ui/hooks'\n\nfunction Layout({ children }) {\n const breakpoint = useBreakpoint({ sm: 640, md: 768, lg: 1024 })\n const isMobile = breakpoint === 'sm'\n\n return isMobile ? <MobileLayout>{children}</MobileLayout> : <DesktopLayout>{children}</DesktopLayout>\n}\n\\`\\`\\`\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const blacksmithCliSkill: Skill = {\n id: 'blacksmith-cli',\n name: 'Blacksmith CLI',\n description: 'CLI commands, configuration, and workflows for project scaffolding and management.',\n\n render(_ctx: SkillContext): string {\n return `## Blacksmith CLI\n\nBlacksmith is the CLI that scaffolded and manages this project. It lives outside the project directory as a globally installed npm package.\n\n### Commands Reference\n\n| Command | Description |\n|---|---|\n| \\`blacksmith init [name]\\` | Create a new project (interactive prompts if no flags) |\n| \\`blacksmith dev\\` | Start Django + Vite + OpenAPI watcher in parallel |\n| \\`blacksmith sync\\` | Regenerate frontend API client from Django OpenAPI schema |\n| \\`blacksmith make:resource <Name>\\` | Scaffold a full CRUD resource across backend and frontend |\n| \\`blacksmith build\\` | Production build (Vite build + Django collectstatic) |\n| \\`blacksmith eject\\` | Remove Blacksmith dependency, keep a clean project |\n| \\`blacksmith setup:ai\\` | Generate CLAUDE.md with AI development skills |\n| \\`blacksmith skills\\` | List all available AI development skills |\n\n### Configuration\n\nProject settings are stored in \\`blacksmith.config.json\\` at the project root:\n\n\\`\\`\\`json\n{\n \"name\": \"my-app\",\n \"version\": \"0.1.0\",\n \"backend\": { \"port\": 8000 },\n \"frontend\": { \"port\": 5173 }\n}\n\\`\\`\\`\n\n- **Ports** are read by \\`blacksmith dev\\` and \\`blacksmith sync\\` — change them here, not in code\n- The CLI finds the project root by walking up directories looking for this file\n\n### How \\`blacksmith dev\\` Works\n\nRuns three concurrent processes:\n1. **Django** — \\`./venv/bin/python manage.py runserver 0.0.0.0:<backend-port>\\`\n2. **Vite** — \\`npm run dev\\` in the frontend directory\n3. **OpenAPI watcher** — watches \\`.py\\` files in backend, runs \\`npx openapi-ts\\` on changes (2s debounce)\n\nAll three are managed by \\`concurrently\\` and stop together on Ctrl+C.\n\n### How \\`blacksmith make:resource\\` Works\n\nGiven a PascalCase name (e.g. \\`BlogPost\\`), it generates:\n\n**Backend:**\n- \\`backend/apps/blog_posts/models.py\\` — Django model with timestamps\n- \\`backend/apps/blog_posts/serializers.py\\` — DRF ModelSerializer\n- \\`backend/apps/blog_posts/views.py\\` — DRF ModelViewSet with drf-spectacular schemas\n- \\`backend/apps/blog_posts/urls.py\\` — DefaultRouter registration\n- \\`backend/apps/blog_posts/admin.py\\` — Admin registration\n- Wires the app into \\`INSTALLED_APPS\\` and \\`config/urls.py\\`\n- Runs \\`makemigrations\\` and \\`migrate\\`\n\n**Frontend:**\n- \\`frontend/src/features/blog-posts/\\` — Feature module with hooks and components\n- \\`frontend/src/pages/blog-posts/\\` — List and detail pages\n- Registers route path in \\`frontend/src/router/paths.ts\\` (\\`Path\\` enum)\n- Registers routes in \\`frontend/src/router/routes.tsx\\`\n\nThen runs \\`blacksmith sync\\` to generate the TypeScript API client.\n\n### How \\`blacksmith sync\\` Works\n\n1. Fetches the OpenAPI schema from \\`http://localhost:<backend-port>/api/schema/\\`\n2. Runs \\`openapi-ts\\` to generate TypeScript types, Zod schemas, SDK functions, and TanStack Query hooks\n3. Output goes to \\`frontend/src/api/generated/\\` — never edit these files manually\n\n### Init Flags\n\n\\`blacksmith init\\` supports both interactive prompts and CLI flags:\n\n\\`\\`\\`bash\n# Fully interactive\nblacksmith init\n\n# Skip prompts with flags\nblacksmith init my-app -b 9000 -f 3000 --ai\n\\`\\`\\`\n\n| Flag | Description |\n|---|---|\n| \\`-b, --backend-port <port>\\` | Django port (default: 8000) |\n| \\`-f, --frontend-port <port>\\` | Vite port (default: 5173) |\n| \\`--ai\\` | Generate CLAUDE.md with project skills |\n| \\`--no-blacksmith-ui-skill\\` | Exclude blacksmith-ui skill from CLAUDE.md |\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const uiDesignSkill: Skill = {\n id: 'ui-design',\n name: 'UI/UX Design System',\n description: 'Modern flat design principles, spacing, typography, color, layout patterns, and interaction guidelines aligned with the BlacksmithUI design language.',\n\n render(_ctx: SkillContext): string {\n return `## UI/UX Design System — Modern Flat Design\n\n> **Design philosophy: Clean, flat, content-first.**\n> BlacksmithUI follows the same design language as Anthropic, Apple, Linear, Vercel, and OpenAI — minimal chrome, generous whitespace, subtle depth, and purposeful motion. Every UI you build must conform to this standard.\n\n### Core Principles\n\n1. **Flat over skeuomorphic** — No gradients on surfaces, no heavy drop shadows, no bevels. Use solid colors, subtle borders, and minimal \\`shadow-sm\\` / \\`shadow-md\\` only where elevation is meaningful (cards, dropdowns, modals).\n2. **Content over decoration** — UI exists to present content, not to look busy. Remove any element that doesn't serve the user. If a section looks empty, the content is the problem — not the lack of decorative elements.\n3. **Whitespace is a feature** — Generous padding and margins create hierarchy and breathing room. Cramped UIs feel cheap. When in doubt, add more space.\n4. **Consistency over creativity** — Every page should feel like part of the same app. Use the same spacing scale, the same component patterns, the same interaction behaviors everywhere.\n5. **Progressive disclosure** — Show only what's needed at each level. Use expandable sections, tabs, dialogs, and drill-down navigation to manage complexity. Don't overwhelm with everything at once.\n\n### Spacing System\n\nUse Tailwind's spacing scale consistently. Do NOT use arbitrary values (\\`p-[13px]\\`) — stick to the system.\n\n| Scale | Value | Use for |\n|-------|-------|---------|\n| \\`1\\`–\\`2\\` | 4–8px | Inline gaps, icon-to-text spacing, tight badge padding |\n| \\`3\\`–\\`4\\` | 12–16px | Inner component padding, gap between related items |\n| \\`5\\`–\\`6\\` | 20–24px | Card padding, section inner spacing |\n| \\`8\\` | 32px | Gap between sections within a page |\n| \\`10\\`–\\`12\\` | 40–48px | Gap between major page sections |\n| \\`16\\`–\\`20\\` | 64–80px | Page-level vertical padding (hero, landing sections) |\n\n**Rules:**\n- Use \\`gap\\` (via \\`Flex\\`, \\`Stack\\`, \\`Grid\\`) for spacing between siblings — not margin on individual items\n- Use \\`Stack gap={...}\\` for vertical rhythm within a section\n- Page content padding: \\`px-4 sm:px-6 lg:px-8\\` (use \\`Container\\` which handles this)\n- Card body padding: \\`p-6\\` standard, \\`p-4\\` for compact cards\n- Never mix spacing approaches in the same context — pick gap OR margin, not both\n\n### Typography\n\nUse \\`Typography\\` and \\`Text\\` components from \\`@blacksmith-ui/react\\`. Do NOT style raw HTML headings.\n\n**Hierarchy:**\n| Level | Component | Use for |\n|-------|-----------|---------|\n| Page title | \\`<Typography variant=\"h1\">\\` | One per page. The main heading. |\n| Section title | \\`<Typography variant=\"h2\">\\` | Major sections within a page |\n| Sub-section | \\`<Typography variant=\"h3\">\\` | Groups within a section |\n| Card title | \\`<Typography variant=\"h4\">\\` or \\`CardTitle\\` | Card headings |\n| Body | \\`<Text>\\` | Paragraphs, descriptions |\n| Caption/label | \\`<Text size=\"sm\" color=\"muted\">\\` | Secondary info, metadata, timestamps |\n| Overline | \\`<Text size=\"xs\" weight=\"medium\" className=\"uppercase tracking-wide\">\\` | Category labels, section overlines |\n\n**Rules:**\n- One \\`h1\\` per page — it's the page title\n- Headings should never skip levels (h1 → h3 without h2)\n- Body text: \\`text-sm\\` (14px) for dense UIs (tables, sidebars), \\`text-base\\` (16px) for reading content\n- Line height: use Tailwind defaults (\\`leading-relaxed\\` for body copy, \\`leading-tight\\` for headings)\n- Max reading width: \\`max-w-prose\\` (~65ch) for long-form text. Never let paragraphs stretch full-width\n- Use \\`text-muted-foreground\\` for secondary text, never gray hardcoded values\n- Font weight: \\`font-medium\\` (500) for labels and emphasis, \\`font-semibold\\` (600) for headings, \\`font-bold\\` (700) sparingly\n\n### Color\n\nUse design tokens (CSS variables), never hardcoded colors.\n\n**Semantic palette:**\n| Token | Usage |\n|-------|-------|\n| \\`primary\\` | Primary actions (buttons, links, active states) |\n| \\`secondary\\` | Secondary actions, subtle backgrounds |\n| \\`destructive\\` | Delete, error, danger states |\n| \\`muted\\` | Backgrounds for subtle sections, disabled states |\n| \\`accent\\` | Highlights, hover states, focus rings |\n| \\`foreground\\` | Primary text |\n| \\`muted-foreground\\` | Secondary/helper text |\n| \\`border\\` | Borders, dividers |\n| \\`card\\` | Card backgrounds |\n| \\`background\\` | Page background |\n\n**Rules:**\n- NEVER use Tailwind color literals (\\`text-gray-500\\`, \\`bg-blue-600\\`, \\`border-slate-200\\`, \\`bg-white\\`, \\`bg-black\\`). Always use semantic tokens (\\`text-muted-foreground\\`, \\`bg-primary\\`, \\`border-border\\`, \\`bg-background\\`). This is non-negotiable — hardcoded colors break dark mode.\n- Status colors: use \\`Badge\\` variants (\\`default\\`, \\`secondary\\`, \\`destructive\\`, \\`outline\\`) — don't hand-roll colored pills.\n- Maximum 2–3 colors visible at any time (primary + foreground + muted). Colorful UIs feel noisy.\n- Every UI must render correctly in both light and dark mode. See the Dark Mode section below for the full rules.\n\n### Layout Patterns\n\n**Page layout:**\n\\`\\`\\`tsx\n<Box as=\"main\">\n <Container>\n <Stack gap={8}>\n {/* Page header */}\n <Flex align=\"center\" justify=\"between\">\n <Stack gap={1}>\n <Typography variant=\"h1\">Page Title</Typography>\n <Text color=\"muted\">Brief description of this page</Text>\n </Stack>\n <Button>Primary Action</Button>\n </Flex>\n\n {/* Page content sections */}\n <Stack gap={6}>\n {/* ... */}\n </Stack>\n </Stack>\n </Container>\n</Box>\n\\`\\`\\`\n\n**Card-based content:**\n\\`\\`\\`tsx\n<Grid columns={{ base: 1, md: 2, lg: 3 }} gap={6}>\n {items.map((item) => (\n <Card key={item.id}>\n <CardHeader>\n <CardTitle>{item.title}</CardTitle>\n <CardDescription>{item.description}</CardDescription>\n </CardHeader>\n <CardContent>\n {/* Content */}\n </CardContent>\n </Card>\n ))}\n</Grid>\n\\`\\`\\`\n\n**Sidebar + main content:**\n\\`\\`\\`tsx\n<Flex className=\"min-h-screen\">\n <Sidebar>{/* Nav items */}</Sidebar>\n <Box as=\"main\" className=\"flex-1\">\n <Container>{/* Page content */}</Container>\n </Box>\n</Flex>\n\\`\\`\\`\n\n**Section with centered content (landing pages):**\n\\`\\`\\`tsx\n<Box as=\"section\" className=\"py-16 sm:py-20\">\n <Container>\n <Stack gap={4} align=\"center\" className=\"text-center\">\n <Typography variant=\"h2\">Section Title</Typography>\n <Text color=\"muted\" className=\"max-w-2xl\">\n A concise description that explains the value proposition.\n </Text>\n </Stack>\n <Grid columns={{ base: 1, md: 3 }} gap={8} className=\"mt-12\">\n {/* Feature cards or content */}\n </Grid>\n </Container>\n</Box>\n\\`\\`\\`\n\n### Component Patterns\n\n**Empty states:**\n\\`\\`\\`tsx\n// GOOD — uses EmptyState component\n<EmptyState\n icon={Inbox}\n title=\"No messages yet\"\n description=\"Messages from your team will appear here.\"\n action={<Button>Send a message</Button>}\n/>\n\n// BAD — hand-rolled empty state\n<div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <Inbox className=\"h-12 w-12 text-gray-400 mb-4\" />\n <h3 className=\"text-lg font-medium\">No messages yet</h3>\n <p className=\"text-gray-500 mt-1\">Messages from your team will appear here.</p>\n</div>\n\\`\\`\\`\n\n**Stats/metrics:**\n\\`\\`\\`tsx\n// GOOD — uses StatCard\n<Grid columns={{ base: 1, sm: 2, lg: 4 }} gap={4}>\n <StatCard label=\"Total Users\" value=\"2,847\" trend=\"+12%\" />\n <StatCard label=\"Revenue\" value=\"$48,290\" trend=\"+8%\" />\n</Grid>\n\n// BAD — hand-rolled stat cards\n<div className=\"grid grid-cols-4 gap-4\">\n <div className=\"bg-white rounded-lg p-6 shadow\">\n <p className=\"text-sm text-gray-500\">Total Users</p>\n <p className=\"text-2xl font-bold\">2,847</p>\n </div>\n</div>\n\\`\\`\\`\n\n**Loading states:**\n\\`\\`\\`tsx\n// GOOD — Skeleton matches the layout structure\n<Stack gap={4}>\n <Skeleton className=\"h-8 w-48\" /> {/* Title */}\n <Skeleton className=\"h-4 w-96\" /> {/* Description */}\n <Grid columns={3} gap={4}>\n {Array.from({ length: 3 }).map((_, i) => (\n <Skeleton key={i} className=\"h-32\" />\n ))}\n </Grid>\n</Stack>\n\n// BAD — generic spinner with no layout hint\n<div className=\"flex justify-center py-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-blue-500 rounded-full\" />\n</div>\n\\`\\`\\`\n\n### Dark Mode & Light Mode\n\n> **CRITICAL: Every screen, component, and custom style MUST look correct in both light and dark mode. No exceptions.**\n\nBlacksmithUI uses the \\`.dark\\` class strategy on \\`<html>\\`. All semantic CSS variables automatically switch between light and dark values. Your job is to never break this.\n\n**Rules:**\n- NEVER hardcode colors. \\`text-gray-500\\`, \\`bg-white\\`, \\`bg-slate-900\\`, \\`border-gray-200\\` — all of these break in one mode or the other. Use semantic tokens: \\`text-muted-foreground\\`, \\`bg-background\\`, \\`bg-card\\`, \\`border-border\\`.\n- NEVER use \\`bg-white\\` or \\`bg-black\\`. Use \\`bg-background\\` (page), \\`bg-card\\` (elevated surfaces), \\`bg-muted\\` (subtle sections).\n- NEVER use \\`text-black\\` or \\`text-white\\`. Use \\`text-foreground\\` (primary text), \\`text-muted-foreground\\` (secondary), \\`text-primary-foreground\\` (text on primary-colored backgrounds).\n- NEVER use hardcoded shadows like \\`shadow-[0_2px_8px_rgba(0,0,0,0.1)]\\`. Use Tailwind shadow utilities (\\`shadow-sm\\`, \\`shadow-md\\`) which respect the theme.\n- NEVER use opacity-based overlays with hardcoded colors (\\`bg-black/50\\`). Use \\`bg-background/80\\` or let overlay components (\\`Dialog\\`, \\`Sheet\\`) handle it.\n- SVG fills and strokes: use \\`currentColor\\` or \\`fill-foreground\\` / \\`stroke-border\\` — never \\`fill-black\\` or \\`stroke-gray-300\\`.\n- Image assets: if you use decorative images or illustrations, ensure they work on both backgrounds or use \\`dark:hidden\\` / \\`hidden dark:block\\` to swap variants.\n\n**Safe color tokens (always use these):**\n| Need | Light mode maps to | Dark mode maps to | Use |\n|------|----|----|-----|\n| Page background | white/light gray | near-black | \\`bg-background\\` |\n| Card/surface | white | dark gray | \\`bg-card\\` |\n| Subtle background | light gray | darker gray | \\`bg-muted\\` |\n| Primary text | near-black | near-white | \\`text-foreground\\` |\n| Secondary text | medium gray | lighter gray | \\`text-muted-foreground\\` |\n| Borders | light gray | dark gray | \\`border-border\\` |\n| Input borders | light gray | dark gray | \\`border-input\\` |\n| Focus ring | brand color | brand color | \\`ring-ring\\` |\n| Primary action | brand color | brand color | \\`bg-primary text-primary-foreground\\` |\n| Destructive | red | red | \\`bg-destructive text-destructive-foreground\\` |\n\n**Testing checklist (mental model):**\nBefore considering any UI complete, verify these in your head:\n1. Does every text element use \\`foreground\\`, \\`muted-foreground\\`, or \\`*-foreground\\` tokens?\n2. Does every background use \\`background\\`, \\`card\\`, \\`muted\\`, or \\`primary\\`/\\`secondary\\`/\\`accent\\` tokens?\n3. Does every border use \\`border\\`, \\`input\\`, or \\`ring\\` tokens?\n4. Are there ANY hex values, rgb values, or Tailwind color names (gray, slate, blue, etc.) in the code? If yes, replace them.\n5. Do hover/focus/active states also use semantic tokens? (\\`hover:bg-muted\\` not \\`hover:bg-gray-100\\`)\n\n### Interactions & Feedback\n\n- **Hover states**: Subtle background change (\\`hover:bg-muted\\`) — not color shifts or scale transforms\n- **Focus**: Use focus-visible ring (\\`focus-visible:ring-2 ring-ring\\`). BlacksmithUI components handle this automatically\n- **Transitions**: \\`transition-colors duration-150\\` for color changes. No bounces, no springs, no dramatic animations\n- **Click feedback**: Use \\`active:scale-[0.98]\\` only on buttons and interactive cards, never on text or static elements\n- **Loading feedback**: Show \\`Spinner\\` on buttons during async actions. Use \\`Skeleton\\` for content areas. Never leave the user without feedback during loading\n- **Success/error feedback**: Use \\`useToast()\\` for transient confirmations. Use \\`Alert\\` for persistent messages. Never use \\`window.alert()\\`\n- **Confirmation before destructive actions**: Always use \\`AlertDialog\\` for delete/remove actions. Never delete on single click\n\n### Responsive Design\n\n- **Mobile-first**: Write base styles for mobile, add \\`sm:\\`/\\`md:\\`/\\`lg:\\` for larger screens\n- **Breakpoints**: \\`sm\\` (640px), \\`md\\` (768px), \\`lg\\` (1024px), \\`xl\\` (1280px)\n- **Grid collapse**: \\`Grid columns={{ base: 1, md: 2, lg: 3 }}\\` — single column on mobile, expand on larger screens\n- **Hide/show**: Use \\`hidden md:block\\` / \\`md:hidden\\` to toggle elements across breakpoints\n- **Touch targets**: Minimum 44×44px for interactive elements on mobile. Use \\`Button size=\"lg\"\\` and adequate padding\n- **Stack on mobile, row on desktop**: Use \\`Flex direction={{ base: 'column', md: 'row' }}\\` or \\`Stack\\` that switches direction\n- **Container**: Always wrap page content in \\`<Container>\\` — it handles responsive horizontal padding\n\n### Anti-Patterns — NEVER Do These\n\n| Anti-pattern | What to do instead |\n|---|---|\n| Hardcoded colors (\\`text-gray-500\\`, \\`bg-blue-600\\`) | Use semantic tokens (\\`text-muted-foreground\\`, \\`bg-primary\\`) |\n| Heavy box shadows (\\`shadow-xl\\`, \\`shadow-2xl\\`) | Use \\`shadow-sm\\` on cards, \\`shadow-md\\` on elevated overlays only |\n| Rounded pill shapes (\\`rounded-full\\`) on cards/containers | Use \\`rounded-lg\\` or \\`rounded-md\\` (controlled by \\`--radius\\`) |\n| Gradient backgrounds on surfaces | Use solid \\`bg-card\\` or \\`bg-background\\` |\n| Decorative borders (\\`border-l-4 border-blue-500\\`) | Use \\`Divider\\` or \\`border-border\\` |\n| Custom scrollbars with CSS hacks | Use \\`ScrollArea\\` |\n| Animated entrances (fade-in, slide-up on mount) | Content should appear instantly. Only animate user-triggered changes |\n| Centering with \\`absolute inset-0 flex items-center\\` | Use \\`Flex align=\"center\" justify=\"center\"\\` |\n| Using \\`<br />\\` for spacing | Use \\`Stack gap={...}\\` or margin utilities |\n| Multiple font sizes in close proximity | Keep nearby text within 1–2 size steps |\n| Dense walls of text | Break into sections with headings, cards, or spacing |\n| Colored backgrounds on every section | Use \\`bg-background\\` as default, \\`bg-muted\\` sparingly for contrast |\n| Over-using badges/tags on everything | Badges are for status and categories, not decoration |\n| Inline styles (\\`style={{ ... }}\\`) | Use Tailwind classes via \\`className\\` |\n| \\`bg-white\\` / \\`bg-black\\` / \\`bg-slate-*\\` | Use \\`bg-background\\`, \\`bg-card\\`, \\`bg-muted\\` |\n| \\`text-black\\` / \\`text-white\\` / \\`text-gray-*\\` | Use \\`text-foreground\\`, \\`text-muted-foreground\\` |\n| \\`border-gray-*\\` / \\`border-slate-*\\` | Use \\`border-border\\`, \\`border-input\\` |\n| Hex/rgb values in className or style | Use CSS variable tokens exclusively |\n| UI that only looks right in light mode | Always verify both modes — use semantic tokens throughout |\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const programmingParadigmsSkill: Skill = {\n id: 'programming-paradigms',\n name: 'Programming Paradigms',\n description: 'Functional programming for React frontend development, OOP + functional patterns for Django backend development.',\n\n render(_ctx: SkillContext): string {\n return `## Programming Paradigms\n\n> **Frontend (React/TypeScript): Functional programming.** Pure functions, immutability, composition, declarative UI.\n> **Backend (Django/Python): OOP with functional touches.** Classes for structure, pure functions for logic, no mutation where avoidable.\n\n---\n\n## Frontend — Functional Programming\n\nReact is a functional framework. Write it functionally. No classes, no imperative mutation, no object-oriented patterns.\n\n### Core Rules\n\n1. **Functions, not classes** — Every component is a function. Every hook is a function. Every utility is a function. Never use \\`class\\` in frontend code.\n2. **Pure by default** — A component given the same props should render the same output. A utility given the same arguments should return the same result. Side effects belong in hooks (\\`useEffect\\`, \\`useMutation\\`), never in render logic.\n3. **Immutable data** — Never mutate state, props, or variables. Always return new values.\n4. **Declarative over imperative** — Describe *what* to render, not *how* to render it. Use \\`map\\`, \\`filter\\`, ternaries, and composition — not \\`for\\` loops, \\`if/else\\` chains, or DOM manipulation.\n5. **Composition over inheritance** — Build complex behavior by composing small functions and components, not by extending base classes.\n\n### Immutability\n\n\\`\\`\\`tsx\n// BAD — mutation\nconst handleAdd = (item) => {\n items.push(item) // mutates array\n setItems(items) // React won't re-render (same reference)\n}\n\nuser.name = 'New Name' // mutates object\nsetUser(user)\n\n// GOOD — immutable updates\nconst handleAdd = (item) => {\n setItems((prev) => [...prev, item])\n}\n\nsetUser((prev) => ({ ...prev, name: 'New Name' }))\n\n// GOOD — immutable array operations\nconst removeItem = (id) => setItems((prev) => prev.filter((i) => i.id !== id))\nconst updateItem = (id, data) => setItems((prev) =>\n prev.map((i) => (i.id === id ? { ...i, ...data } : i))\n)\n\\`\\`\\`\n\n### Pure Functions & Composition\n\n\\`\\`\\`tsx\n// BAD — impure, relies on external state\nlet taxRate = 0.1\nconst calculateTotal = (price) => price * (1 + taxRate)\n\n// GOOD — pure, all inputs explicit\nconst calculateTotal = (price: number, taxRate: number) => price * (1 + taxRate)\n\n// GOOD — compose small functions\nconst formatCurrency = (amount: number) => \\`$\\${amount.toFixed(2)}\\`\nconst calculateTax = (price: number, rate: number) => price * rate\nconst formatPriceWithTax = (price: number, rate: number) =>\n formatCurrency(price + calculateTax(price, rate))\n\\`\\`\\`\n\n### Declarative UI Patterns\n\n\\`\\`\\`tsx\n// BAD — imperative rendering\nfunction UserList({ users }) {\n const items = []\n for (let i = 0; i < users.length; i++) {\n if (users[i].isActive) {\n items.push(<UserCard key={users[i].id} user={users[i]} />)\n }\n }\n return <div>{items}</div>\n}\n\n// GOOD — declarative rendering\nfunction UserList({ users }) {\n return (\n <Stack gap={4}>\n {users\n .filter((user) => user.isActive)\n .map((user) => <UserCard key={user.id} user={user} />)\n }\n </Stack>\n )\n}\n\n// BAD — imperative conditional\nfunction Status({ isOnline }) {\n let badge\n if (isOnline) {\n badge = <Badge>Online</Badge>\n } else {\n badge = <Badge variant=\"secondary\">Offline</Badge>\n }\n return badge\n}\n\n// GOOD — declarative conditional\nfunction Status({ isOnline }) {\n return isOnline\n ? <Badge>Online</Badge>\n : <Badge variant=\"secondary\">Offline</Badge>\n}\n\\`\\`\\`\n\n### Hooks as Functional Composition\n\n\\`\\`\\`tsx\n// BAD — logic in component body\nfunction OrdersPage() {\n const [page, setPage] = useState(1)\n const [search, setSearch] = useState('')\n const debounced = useDebounce(search, 300)\n const { data } = useApiQuery(ordersListOptions({ query: { page, search: debounced } }))\n const deleteOrder = useApiMutation(ordersDestroyMutation())\n\n // ... 20 lines of derived state and handlers\n\n return ( /* JSX */ )\n}\n\n// GOOD — compose hooks, component just renders\nfunction OrdersPage() {\n const { orders, pagination, search, deleteOrder } = useOrdersPage()\n\n return (\n <Stack gap={4}>\n <OrdersToolbar search={search} />\n <OrdersTable orders={orders} onDelete={deleteOrder} />\n <Pagination {...pagination} />\n </Stack>\n )\n}\n\n// The hook composes smaller hooks\nfunction useOrdersPage() {\n const search = useSearchFilter()\n const pagination = usePagination()\n const { data } = useOrders({ page: pagination.page, search: search.debounced })\n const deleteOrder = useDeleteOrder()\n\n return {\n orders: data?.results ?? [],\n pagination: { ...pagination, total: data?.count ?? 0 },\n search,\n deleteOrder: (id: string) => deleteOrder.mutate({ path: { id } }),\n }\n}\n\\`\\`\\`\n\n### Data Transformation — Functional Style\n\n\\`\\`\\`tsx\n// BAD — imperative transformation\nfunction getActiveUserNames(users) {\n const result = []\n for (const user of users) {\n if (user.isActive) {\n result.push(user.name.toUpperCase())\n }\n }\n return result\n}\n\n// GOOD — functional pipeline\nconst getActiveUserNames = (users: User[]) =>\n users\n .filter((u) => u.isActive)\n .map((u) => u.name.toUpperCase())\n\n// GOOD — derive state without mutation\nconst sortedItems = useMemo(\n () => [...items].sort((a, b) => a.name.localeCompare(b.name)),\n [items]\n)\n\nconst groupedByStatus = useMemo(\n () => items.reduce<Record<string, Item[]>>((acc, item) => ({\n ...acc,\n [item.status]: [...(acc[item.status] ?? []), item],\n }), {}),\n [items]\n)\n\\`\\`\\`\n\n### What to Avoid in Frontend Code\n\n| Anti-pattern | Functional alternative |\n|---|---|\n| \\`class MyComponent extends React.Component\\` | \\`function MyComponent()\\` |\n| \\`this.state\\`, \\`this.setState\\` | \\`useState\\`, \\`useReducer\\` |\n| \\`array.push()\\`, \\`object.key = value\\` | Spread: \\`[...arr, item]\\`, \\`{ ...obj, key: value }\\` |\n| \\`for\\` / \\`while\\` loops in render | \\`.map()\\`, \\`.filter()\\`, \\`.reduce()\\` |\n| \\`let\\` for derived values | \\`const\\` + \\`useMemo\\` or inline computation |\n| Mutable ref for state (\\`useRef\\` to track values) | \\`useState\\` or \\`useReducer\\` |\n| HOCs (\\`withAuth\\`, \\`withTheme\\`) | Custom hooks (\\`useAuth\\`, \\`useTheme\\`) |\n| Render props for logic sharing | Custom hooks |\n| \\`if/else\\` chains for rendering | Ternaries, \\`&&\\`, early returns, lookup objects |\n| Singleton services / global mutable state | Context + hooks, React Query for server state |\n\n---\n\n## Backend — OOP with Functional Patterns\n\nDjango is object-oriented by design. Lean into it for structure (models, views, serializers, services), but use functional patterns for pure logic and data transformations.\n\n### OOP for Structure\n\n**Models** — Encapsulate data and behavior together:\n\\`\\`\\`python\nclass Order(TimeStampedModel):\n user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='orders')\n status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')\n total = models.DecimalField(max_digits=10, decimal_places=2, default=0)\n\n class Meta:\n ordering = ['-created_at']\n\n def __str__(self):\n return f\"Order #{self.pk} — {self.user}\"\n\n @property\n def is_cancellable(self):\n return self.status in ('pending', 'confirmed')\n\n def recalculate_total(self):\n self.total = self.items.aggregate(\n total=Sum(F('quantity') * F('unit_price'))\n )['total'] or 0\n self.save(update_fields=['total'])\n\\`\\`\\`\n\n**Services** — Classes for complex business operations with multiple related methods:\n\\`\\`\\`python\nclass OrderService:\n @staticmethod\n @transaction.atomic\n def place_order(*, user, items, shipping_address):\n order = Order.objects.create(user=user, shipping_address=shipping_address)\n for item_data in items:\n OrderItem.objects.create(order=order, **item_data)\n order.recalculate_total()\n NotificationService.send_order_confirmation(order=order)\n return order\n\n @staticmethod\n @transaction.atomic\n def cancel_order(*, order, user):\n if not order.is_cancellable:\n raise ValidationError(\"Order cannot be cancelled in its current state.\")\n if order.user != user:\n raise PermissionDenied(\"You can only cancel your own orders.\")\n order.status = 'cancelled'\n order.save(update_fields=['status'])\n InventoryService.restore_stock(order=order)\n return order\n\\`\\`\\`\n\n**ViewSets** — Inherit, extend, override:\n\\`\\`\\`python\nclass OrderViewSet(ModelViewSet):\n permission_classes = [IsAuthenticated]\n\n def get_queryset(self):\n return OrderSelector.list_for_user(user=self.request.user)\n\n def get_serializer_class(self):\n return {\n 'list': OrderListSerializer,\n 'retrieve': OrderDetailSerializer,\n 'create': OrderCreateSerializer,\n }.get(self.action, OrderUpdateSerializer)\n\n def perform_create(self, serializer):\n serializer.save()\n\\`\\`\\`\n\n**Custom permissions, filters, pagination** — All class-based, inheriting from DRF base classes:\n\\`\\`\\`python\nclass IsOwner(BasePermission):\n def has_object_permission(self, request, view, obj):\n return obj.user == request.user\n\nclass OrderFilterSet(django_filters.FilterSet):\n class Meta:\n model = Order\n fields = ['status', 'created_at']\n\\`\\`\\`\n\n### Functional Patterns in Python\n\nUse functional style for pure logic, data transformation, and utilities — anywhere you don't need state or inheritance.\n\n**Selectors** — Pure query builders (can be functions or static methods):\n\\`\\`\\`python\n# selectors.py — pure functions that build querysets\ndef get_active_orders(*, user, status=None):\n qs = (\n Order.objects\n .filter(user=user, is_active=True)\n .select_related('user')\n .prefetch_related('items__product')\n )\n if status:\n qs = qs.filter(status=status)\n return qs.order_by('-created_at')\n\ndef get_order_summary(*, user):\n return (\n Order.objects\n .filter(user=user)\n .values('status')\n .annotate(count=Count('id'), total=Sum('total'))\n )\n\\`\\`\\`\n\n**Data transformations** — Use comprehensions, \\`map\\`, pure functions:\n\\`\\`\\`python\n# BAD — imperative mutation\ndef format_export_data(orders):\n result = []\n for order in orders:\n row = {}\n row['id'] = order.id\n row['total'] = str(order.total)\n row['items'] = ', '.join([i.product.name for i in order.items.all()])\n result.append(row)\n return result\n\n# GOOD — functional transformation\ndef format_export_data(orders):\n return [\n {\n 'id': order.id,\n 'total': str(order.total),\n 'items': ', '.join(i.product.name for i in order.items.all()),\n }\n for order in orders\n ]\n\\`\\`\\`\n\n**Utility functions** — Pure, no side effects:\n\\`\\`\\`python\n# utils.py — all pure functions\ndef calculate_discount(price: Decimal, percentage: int) -> Decimal:\n return price * (Decimal(percentage) / 100)\n\ndef slugify_unique(name: str, existing_slugs: set[str]) -> str:\n base = slugify(name)\n slug = base\n counter = 1\n while slug in existing_slugs:\n slug = f\"{base}-{counter}\"\n counter += 1\n return slug\n\ndef paginate_list(items: list, page: int, page_size: int = 20) -> list:\n start = (page - 1) * page_size\n return items[start:start + page_size]\n\\`\\`\\`\n\n**Decorators** — Functional composition for cross-cutting concerns:\n\\`\\`\\`python\nimport functools\nimport logging\n\nlogger = logging.getLogger(__name__)\n\ndef log_service_call(func):\n @functools.wraps(func)\n def wrapper(*args, **kwargs):\n logger.info(f\"Calling {func.__name__} with kwargs={kwargs}\")\n result = func(*args, **kwargs)\n logger.info(f\"{func.__name__} completed successfully\")\n return result\n return wrapper\n\n# Usage\nclass OrderService:\n @staticmethod\n @log_service_call\n @transaction.atomic\n def place_order(*, user, items, shipping_address):\n ...\n\\`\\`\\`\n\n### When to Use What\n\n| Pattern | Use OOP (class) | Use Functional (function) |\n|---------|-----------------|---------------------------|\n| **Models** | Always — Django models are classes | Model methods can be property-style pure computations |\n| **Views** | Always — ViewSets, APIViews | — |\n| **Serializers** | Always — DRF serializers are classes | — |\n| **Services** | Business logic with multiple related operations | Single-purpose operations can be standalone functions |\n| **Selectors** | Either — class with static methods or module-level functions | Preferred — pure functions that return querysets |\n| **Permissions** | Always — DRF permissions are class-based | — |\n| **Filters** | Always — django-filter uses classes | — |\n| **Utilities** | Never — don't wrap utilities in classes | Always — pure functions |\n| **Data transforms** | Never | Always — comprehensions, map, pure functions |\n| **Validators** | DRF validator classes for reusable validation | Simple validation functions for one-off checks |\n| **Signals** | Receiver functions (decorated functions) | — |\n| **Tests** | Test classes inheriting APITestCase | Individual test functions with pytest are also fine |\n\n### Backend Anti-Patterns\n\n| Anti-pattern | Correct approach |\n|---|---|\n| God class with 20+ methods | Split into focused Service + Selector + utils |\n| Utility class with only static methods | Use module-level functions instead |\n| Mixin soup (\\`class View(A, B, C, D, E)\\`) | Compose with max 1-2 mixins, prefer explicit overrides |\n| Business logic in views | Move to services |\n| Business logic in serializers | Serializers validate, services execute |\n| Mutable default arguments (\\`def f(items=[])\\`) | Use \\`None\\` default: \\`def f(items=None)\\` → \\`items = items or []\\` |\n| Nested \\`for\\` loops for data building | List/dict comprehensions |\n| Raw SQL for simple queries | Django ORM with \\`annotate\\`, \\`Subquery\\`, \\`F\\` expressions |\n| Global mutable state | Pass dependencies explicitly, use Django settings for config |\n| Deep inheritance chains | Prefer composition, keep inheritance to 1-2 levels |\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const frontendTestingSkill: Skill = {\n id: 'frontend-testing',\n name: 'Frontend Testing Conventions',\n description: 'Test infrastructure, file placement, test utilities, and rules for when and how to write frontend tests.',\n\n render(_ctx: SkillContext): string {\n return `## Frontend Testing Conventions\n\n### Stack\n- **Vitest** — test runner (configured in \\`vite.config.ts\\`)\n- **jsdom** — browser environment\n- **React Testing Library** — component rendering and queries\n- **\\`@testing-library/user-event\\`** — user interaction simulation\n- **\\`@testing-library/jest-dom\\`** — DOM assertion matchers (e.g. \\`toBeInTheDocument\\`)\n\n### Running Tests\n- Run all tests: \\`cd frontend && npm test\\`\n- Watch mode: \\`cd frontend && npm run test:watch\\`\n- Run a specific file: \\`cd frontend && npx vitest run src/pages/home/__tests__/home.spec.tsx\\`\n- Coverage: \\`cd frontend && npm run test:coverage\\`\n\n### File Placement — Tests Live Next to the Code\n\n> **RULE: Every test file goes in a \\`__tests__/\\` folder co-located with the code it tests. Never put tests in a top-level \\`tests/\\` directory.**\n\n\\`\\`\\`\npages/customers/\n├── customers-page.tsx\n├── customer-detail-page.tsx\n├── __tests__/ # Page integration tests\n│ ├── customers-page.spec.tsx\n│ └── customer-detail-page.spec.tsx\n├── components/\n│ ├── customer-card.tsx\n│ ├── customer-list.tsx\n│ ├── customer-form.tsx\n│ └── __tests__/ # Component unit tests\n│ ├── customer-card.spec.tsx\n│ ├── customer-list.spec.tsx\n│ └── customer-form.spec.tsx\n└── hooks/\n ├── use-customers-page.ts\n └── __tests__/ # Hook tests\n └── use-customers-page.spec.ts\n\\`\\`\\`\n\nThe same pattern applies to \\`features/\\`, \\`shared/\\`, and \\`router/\\`:\n\\`\\`\\`\nfeatures/auth/\n├── pages/\n│ ├── login-page.tsx\n│ └── __tests__/\n│ └── login-page.spec.tsx\n├── hooks/\n│ └── __tests__/\nshared/\n├── components/\n│ └── __tests__/\n├── hooks/\n│ └── __tests__/\nrouter/\n├── __tests__/\n│ ├── paths.spec.ts\n│ └── auth-guard.spec.tsx\n\\`\\`\\`\n\n### Test File Naming\n- Use \\`.spec.tsx\\` for component/page tests (JSX)\n- Use \\`.spec.ts\\` for pure logic tests (hooks, utilities, no JSX)\n- Name matches the source file: \\`customer-card.tsx\\` → \\`customer-card.spec.tsx\\`\n\n### Always Use \\`renderWithProviders\\`\n\n> **RULE: Never import \\`render\\` from \\`@testing-library/react\\` directly. Always use \\`renderWithProviders\\` from \\`@/__tests__/test-utils\\`.**\n\n\\`renderWithProviders\\` wraps components with all app providers (ThemeProvider, QueryClientProvider, MemoryRouter) so tests match the real app environment.\n\n\\`\\`\\`tsx\nimport { screen } from '@/__tests__/test-utils'\nimport { renderWithProviders } from '@/__tests__/test-utils'\nimport { MyComponent } from '../my-component'\n\ndescribe('MyComponent', () => {\n it('renders correctly', () => {\n renderWithProviders(<MyComponent />)\n expect(screen.getByText('Hello')).toBeInTheDocument()\n })\n})\n\\`\\`\\`\n\n**Options:**\n\\`\\`\\`tsx\nrenderWithProviders(<MyComponent />, {\n routerEntries: ['/customers/1'], // Set initial route\n queryClient: customQueryClient, // Custom query client\n})\n\\`\\`\\`\n\n**User interactions:**\n\\`\\`\\`tsx\nconst { user } = renderWithProviders(<MyComponent />)\nawait user.click(screen.getByRole('button', { name: 'Submit' }))\nawait user.type(screen.getByLabelText('Email'), 'test@example.com')\n\\`\\`\\`\n\n### When to Write Tests\n\n> **RULE: Every code change that touches pages, components, hooks, or utilities must include corresponding test updates.**\n\n| What changed | Test required |\n|---|---|\n| New page | Add \\`__tests__/<page>.spec.tsx\\` with integration test |\n| New component | Add \\`__tests__/<component>.spec.tsx\\` |\n| New hook (with logic) | Add \\`__tests__/<hook>.spec.ts\\` |\n| New utility function | Add \\`__tests__/<util>.spec.ts\\` |\n| Modified page/component | Update existing test or add new test cases |\n| Bug fix | Add regression test that would have caught the bug |\n| Deleted page/component | Delete corresponding test file |\n\n### What to Test\n\n**Page integration tests** — test the page as a whole:\n- Renders correct heading/title\n- Loading state shows skeleton or spinner\n- Error state shows error message\n- Data renders correctly (mock the API hooks)\n- User interactions (navigation, form submission, delete confirmation)\n\n**Component unit tests** — test the component in isolation:\n- Renders with required props\n- Handles optional props correctly (present vs absent)\n- Displays correct content based on props\n- User interactions trigger correct callbacks\n- Conditional rendering (empty state, loading state)\n\n**Hook tests** — test custom hooks with logic:\n- Returns correct initial state\n- Transforms data correctly\n- Side effects fire as expected\n\n**Utility/pure function tests** — test input/output:\n- Happy path\n- Edge cases (empty input, null, special characters)\n- Error cases\n\n### Mocking Patterns\n\n**Mock hooks (for page tests):**\n\\`\\`\\`tsx\nvi.mock('@/api/hooks/customers')\nvi.mock('@/features/auth/hooks/use-auth')\n\nimport { useCustomers } from '@/api/hooks/customers'\n\nbeforeEach(() => {\n vi.mocked(useCustomers).mockReturnValue({\n data: { customers: mockCustomers, total: 2 },\n isLoading: false,\n errorMessage: null,\n } as any)\n})\n\\`\\`\\`\n\n**Mock external UI libraries (for auth page tests):**\n\\`\\`\\`tsx\nvi.mock('@blacksmith-ui/auth', () => ({\n LoginForm: ({ onSubmit, error, loading }: any) => (\n <form onSubmit={(e: any) => { e.preventDefault(); onSubmit({ email: 'test@test.com', password: 'pass' }) }}>\n {error && <div data-testid=\"error\">{error.message}</div>}\n <button type=\"submit\">Sign In</button>\n </form>\n ),\n}))\n\\`\\`\\`\n\n**Mock react-router-dom hooks (for detail pages):**\n\\`\\`\\`tsx\nconst mockNavigate = vi.fn()\nvi.mock('react-router-dom', async () => {\n const actual = await vi.importActual('react-router-dom')\n return { ...actual, useParams: () => ({ id: '1' }), useNavigate: () => mockNavigate }\n})\n\\`\\`\\`\n\n### Test Structure\n\\`\\`\\`tsx\nimport { screen, waitFor } from '@/__tests__/test-utils'\nimport { renderWithProviders } from '@/__tests__/test-utils'\n\n// Mocks at the top, before imports of modules that use them\nvi.mock('@/api/hooks/customers')\n\nimport { useCustomers } from '@/api/hooks/customers'\nimport CustomersPage from '../customers-page'\n\nconst mockCustomers = [\n { id: '1', title: 'Acme Corp', created_at: '2024-01-15T10:00:00Z' },\n]\n\ndescribe('CustomersPage', () => {\n beforeEach(() => {\n vi.mocked(useCustomers).mockReturnValue({ ... } as any)\n })\n\n it('renders page heading', () => {\n renderWithProviders(<CustomersPage />)\n expect(screen.getByText('Customers')).toBeInTheDocument()\n })\n\n it('shows error message when API fails', () => {\n vi.mocked(useCustomers).mockReturnValue({\n data: undefined,\n isLoading: false,\n errorMessage: 'Failed to load',\n } as any)\n\n renderWithProviders(<CustomersPage />)\n expect(screen.getByText('Failed to load')).toBeInTheDocument()\n })\n})\n\\`\\`\\`\n\n### Key Rules\n\n1. **Tests live next to code** — \\`__tests__/\\` folder alongside the source, not in a separate top-level directory\n2. **Always use \\`renderWithProviders\\`** — never import render from \\`@testing-library/react\\` directly\n3. **Every page gets an integration test** — at minimum: renders heading, handles loading, handles errors\n4. **Every component gets a unit test** — at minimum: renders with required props, handles optional props\n5. **Mock at the hook level** — mock \\`useCustomers\\`, not \\`fetch\\`. Mock \\`useAuth\\`, not the auth adapter\n6. **Test behavior, not implementation** — query by role, text, or label, not by class names or internal state\n7. **No test-only IDs unless necessary** — prefer \\`getByRole\\`, \\`getByText\\`, \\`getByLabelText\\` over \\`getByTestId\\`\n8. **Keep tests focused** — each \\`it()\\` tests one behavior. Don't assert 10 things in one test\n9. **Clean up mocks** — use \\`beforeEach\\` to reset mock return values so tests don't leak state\n10. **Update tests when code changes** — if you modify a component, update its tests. If you delete a component, delete its tests\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const cleanCodeSkill: Skill = {\n id: 'clean-code',\n name: 'Clean Code Principles',\n description: 'Naming, functions, components, file organization, conditionals, error handling, and DRY guidelines.',\n\n render(_ctx: SkillContext): string {\n return `## Clean Code Principles\n\nWrite code that is easy to read, easy to change, and easy to delete. Treat clarity as a feature.\n\n### Naming\n- Names should reveal intent. A reader should understand what a variable, function, or class does without reading its implementation\n- Booleans: prefix with \\`is\\`, \\`has\\`, \\`can\\`, \\`should\\` — e.g. \\`isLoading\\`, \\`hasPermission\\`, \\`canEdit\\`\n- Functions: use verb phrases that describe the action — e.g. \\`fetchUsers\\`, \\`createOrder\\`, \\`validateEmail\\`\n- Event handlers: prefix with \\`handle\\` in components, \\`on\\` in props — e.g. \\`handleSubmit\\`, \\`onSubmit\\`\n- Collections: use plural nouns — e.g. \\`users\\`, \\`orderItems\\`, not \\`userList\\` or \\`data\\`\n- Avoid abbreviations. \\`transaction\\` not \\`txn\\`, \\`button\\` not \\`btn\\`, \\`message\\` not \\`msg\\`\n- Avoid generic names like \\`data\\`, \\`info\\`, \\`item\\`, \\`result\\`, \\`value\\` unless the scope is trivially small (e.g. a one-line callback)\n\n### Functions\n- A function should do one thing. If you can describe what it does with \"and\", split it\n- Keep functions short — aim for under 20 lines. If a function is longer, look for sections you can extract\n- Prefer early returns to reduce nesting. Guard clauses at the top, happy path at the bottom\n- Limit parameters to 3. Beyond that, pass an options object\n- Pure functions are easier to test, reason about, and reuse. Prefer them where possible\n- Don't use flags (boolean parameters) to make a function do two different things — write two functions instead\n\n### Components (React-specific)\n- One component per file. The file name should match the component name\n- Keep components under 100 lines of JSX. Extract sub-components when they grow beyond this\n- Separate data logic (hooks) from presentation (components). A component should mostly be JSX, not logic\n- **Page components are orchestrators** — they should be ~20-30 lines, composing child components from \\`components/\\` and calling hooks from \\`hooks/\\`. Never build a 200-line page monolith\n- Props interfaces should be explicit and narrow — accept only what the component needs, not entire objects\n- Avoid prop drilling beyond 2 levels — use context or restructure the component tree\n- Destructure props in the function signature for clarity\n- Use \\`@blacksmith-ui/react\\` layout components (\\`Stack\\`, \\`Flex\\`, \\`Grid\\`) — never raw \\`<div>\\` with flex/grid classes\n\n### File Organization\n- Keep files short. If a file exceeds 200 lines, it is likely doing too much — split it\n- Group by feature, not by type. \\`features/orders/\\` is better than \\`components/\\`, \\`hooks/\\`, \\`utils/\\` at the top level\n- Co-locate related code. A component's hook, types, and test should live next to it\n- One export per file for components and hooks. Use \\`index.ts\\` barrel files only at the feature boundary\n\n### Conditionals & Logic\n- Prefer positive conditionals: \\`if (isValid)\\` over \\`if (!isInvalid)\\`\n- Extract complex conditions into well-named variables or functions:\n \\`\\`\\`ts\n // Bad\n if (user.role === 'admin' && user.isActive && !user.isSuspended) { ... }\n\n // Good\n const canAccessAdminPanel = user.role === 'admin' && user.isActive && !user.isSuspended\n if (canAccessAdminPanel) { ... }\n \\`\\`\\`\n- Avoid deeply nested if/else trees. Use early returns, guard clauses, or lookup objects\n- Prefer \\`switch\\` or object maps over long \\`if/else if\\` chains:\n \\`\\`\\`ts\n // Bad\n if (status === 'active') return 'green'\n else if (status === 'pending') return 'yellow'\n else if (status === 'inactive') return 'gray'\n\n // Good\n const statusColor = { active: 'green', pending: 'yellow', inactive: 'gray' }\n return statusColor[status]\n \\`\\`\\`\n\n### Error Handling\n- Handle errors at the right level — close to where they occur and where you can do something meaningful\n- Provide useful error messages that help the developer (or user) understand what went wrong and what to do\n- Don't swallow errors silently. If you catch, log or handle. Never write empty \\`catch {}\\` blocks\n- Use typed errors. In Python, raise specific exceptions. In TypeScript, return discriminated unions or throw typed errors\n\n### Comments\n- Don't comment what the code does — make the code readable enough to not need it\n- Do comment why — explain business decisions, workarounds, non-obvious constraints\n- Delete commented-out code. Version control remembers it\n- TODOs are acceptable but should include context: \\`// TODO(auth): rate-limit login attempts after v1 launch\\`\n\n### DRY Without Overengineering\n- Don't repeat the same logic in multiple places — extract it once you see the third occurrence\n- But don't over-abstract. Two similar blocks of code are fine if they serve different purposes and are likely to diverge\n- Premature abstraction is worse than duplication. Wait for patterns to emerge before creating shared utilities\n- Helper functions should be genuinely reusable. A \"helper\" called from one place is just indirection\n\n### Python-Specific (Django)\n- Use \\`f-strings\\` for string formatting, not \\`.format()\\` or \\`%\\`\n- Use list/dict/set comprehensions when they are clearer than loops — but don't nest them\n- Use \\`dataclasses\\` or typed dicts for structured data outside of Django models\n- Keep view methods thin — push business logic into model methods, serializer validation, or service functions\n- Use \\`get_object_or_404\\` instead of manual \\`try/except DoesNotExist\\`\n\n### TypeScript-Specific (React)\n- Use strict TypeScript. Don't use \\`any\\` — use \\`unknown\\` and narrow, or define a proper type\n- Define interfaces for component props, API responses, and form schemas\n- Use \\`const\\` by default. Only use \\`let\\` when reassignment is necessary. Never use \\`var\\`\n- Prefer \\`map\\`, \\`filter\\`, \\`reduce\\` over imperative loops for data transformation\n- Use optional chaining (\\`?.\\`) and nullish coalescing (\\`??\\`) instead of manual null checks\n- Keep type definitions close to where they are used. Don't create a global \\`types.ts\\` file\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const aiGuidelinesSkill: Skill = {\n id: 'ai-guidelines',\n name: 'AI Development Guidelines',\n description: 'Guidelines for developing the project using AI, including when to use code generation, code style, environment setup, and a checklist before finishing tasks.',\n\n render(_ctx: SkillContext): string {\n return `## AI Development Guidelines\n\n### When Adding Features\n1. Use \\`blacksmith make:resource <Name>\\` for new CRUD resources — it scaffolds model, serializer, viewset, URLs, hooks, components, and pages across both backend and frontend\n2. After any backend API change (new endpoint, changed schema, new field), run \\`blacksmith sync\\` to regenerate the frontend API client and types\n3. Never manually edit files in \\`frontend/src/api/generated/\\` — they are overwritten on every sync\n\n### Code Style\n- **Backend**: Follow PEP 8. Use Django and DRF conventions. Docstrings on models, serializers, and non-obvious view methods\n- **Frontend**: TypeScript strict mode. Functional components. Named exports (not default, except for page components used in routes). Descriptive variable names\n- Use existing patterns in the codebase as reference before inventing new ones\n\n### Frontend Architecture (Mandatory)\n- **Use \\`@blacksmith-ui/react\\` for ALL UI** — \\`Stack\\`, \\`Flex\\`, \\`Grid\\` for layout; \\`Typography\\`, \\`Text\\` for text; \\`Card\\`, \\`Button\\`, \\`Badge\\`, etc. for all elements. Never use raw HTML (\\`<div>\\`, \\`<h1>\\`, \\`<p>\\`, \\`<button>\\`) when a Blacksmith-UI component exists\n- **Pages are thin orchestrators** — compose child components from \\`components/\\`, extract logic into \\`hooks/\\`. A page file should be ~20-30 lines, not a monolith\n- **Use the \\`Path\\` enum** — all route paths come from \\`src/router/paths.ts\\`. Never hardcode path strings like \\`'/login'\\` or \\`'/dashboard'\\`\n- **Add new paths to the enum** — when creating a new page, add its path to the \\`Path\\` enum before the \\`// blacksmith:path\\` marker\n\n### Environment\n- Backend: \\`http://localhost:8000\\`\n- Frontend: \\`http://localhost:5173\\`\n- API docs: \\`http://localhost:8000/api/docs/\\` (Swagger UI) or \\`/api/redoc/\\` (ReDoc)\n- Python venv: \\`backend/venv/\\` — always use \\`./venv/bin/python\\` or \\`./venv/bin/pip\\`\n- Start everything: \\`blacksmith dev\\`\n\n### Checklist Before Finishing a Task\n1. Backend tests pass: \\`cd backend && ./venv/bin/python manage.py test\\`\n2. Frontend tests pass: \\`cd frontend && npm test\\`\n3. Frontend builds: \\`cd frontend && npm run build\\`\n4. API types are in sync: \\`blacksmith sync\\`\n5. No lint errors in modified files\n6. All UI uses \\`@blacksmith-ui/react\\` components — no raw \\`<div>\\` for layout, no raw \\`<h1>\\`-\\`<h6>\\` for text\n7. Pages are modular — page file is a thin orchestrator, sections are in \\`components/\\`, logic in \\`hooks/\\`\n8. Logic is in hooks — no \\`useApiQuery\\`, \\`useApiMutation\\`, \\`useEffect\\`, or multi-\\`useState\\` in component bodies\n9. No hardcoded route paths — all paths use the \\`Path\\` enum from \\`@/router/paths\\`\n10. New routes have a corresponding \\`Path\\` enum entry\n11. **Tests are co-located** — every new or modified page, component, or hook has a corresponding \\`.spec.tsx\\` / \\`.spec.ts\\` in a \\`__tests__/\\` folder next to the source file (see the \\`frontend-testing\\` skill)\n`\n },\n}\n","import net from 'node:net'\nimport concurrently from 'concurrently'\nimport { findProjectRoot, getBackendDir, getFrontendDir, loadConfig } from '../utils/paths.js'\nimport path from 'node:path'\nimport { log } from '../utils/logger.js'\n\nfunction isPortAvailable(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const server = net.createServer()\n server.once('error', () => resolve(false))\n server.once('listening', () => {\n server.close(() => resolve(true))\n })\n server.listen(port)\n })\n}\n\nasync function findAvailablePort(startPort: number): Promise<number> {\n let port = startPort\n while (port < startPort + 100) {\n if (await isPortAvailable(port)) return port\n port++\n }\n throw new Error(`No available port found in range ${startPort}-${port - 1}`)\n}\n\nexport async function dev() {\n let root: string\n try {\n root = findProjectRoot()\n } catch {\n log.error('Not inside a Blacksmith project. Run \"blacksmith init <name>\" first.')\n process.exit(1)\n }\n\n const config = loadConfig(root)\n const backendDir = getBackendDir(root)\n const frontendDir = getFrontendDir(root)\n\n let backendPort: number\n let frontendPort: number\n try {\n ;[backendPort, frontendPort] = await Promise.all([\n findAvailablePort(config.backend.port),\n findAvailablePort(config.frontend.port),\n ])\n } catch (err) {\n log.error((err as Error).message)\n process.exit(1)\n }\n\n if (backendPort !== config.backend.port) {\n log.step(`Backend port ${config.backend.port} in use, using ${backendPort}`)\n }\n if (frontendPort !== config.frontend.port) {\n log.step(`Frontend port ${config.frontend.port} in use, using ${frontendPort}`)\n }\n\n log.info('Starting development servers...')\n log.blank()\n log.step(`Django → http://localhost:${backendPort}`)\n log.step(`Vite → http://localhost:${frontendPort}`)\n log.step(`Swagger → http://localhost:${backendPort}/api/docs/`)\n log.step('OpenAPI sync → watching backend .py files')\n log.blank()\n\n // Build an inline watcher script that watches backend .py files.\n // Runs as a separate child process via concurrently so fs.watch works reliably.\n const syncCmd = `${process.execPath} ${path.join(frontendDir, 'node_modules', '.bin', 'openapi-ts')}`\n const watcherCode = [\n `const{watch}=require(\"fs\"),{exec}=require(\"child_process\");`,\n `let t=null,s=false;`,\n `watch(${JSON.stringify(backendDir)},{recursive:true},(e,f)=>{`,\n `if(!f||!f.endsWith(\".py\"))return;`,\n `if(f.startsWith(\"venv/\")||f.includes(\"__pycache__\")||f.includes(\"/migrations/\"))return;`,\n `if(t)clearTimeout(t);`,\n `t=setTimeout(()=>{`,\n `if(s)return;s=true;`,\n `console.log(\"Backend change detected — syncing OpenAPI types...\");`,\n `exec(${JSON.stringify(syncCmd)},{cwd:${JSON.stringify(frontendDir)}},(err,o,se)=>{`,\n `s=false;`,\n `if(err)console.error(\"Sync failed:\",se||err.message);`,\n `else console.log(\"OpenAPI types synced\");`,\n `})`,\n `},2000)});`,\n `console.log(\"Watching for .py changes...\");`,\n ].join('')\n\n const { result } = concurrently(\n [\n {\n command: `./venv/bin/python manage.py runserver 0.0.0.0:${backendPort}`,\n name: 'django',\n cwd: backendDir,\n prefixColor: 'green',\n },\n {\n command: 'npm run dev',\n name: 'vite',\n cwd: frontendDir,\n prefixColor: 'blue',\n },\n {\n command: `node -e '${watcherCode}'`,\n name: 'sync',\n cwd: frontendDir,\n prefixColor: 'yellow',\n },\n ],\n {\n prefix: 'name',\n killOthers: ['failure'],\n restartTries: 3,\n }\n )\n\n const shutdown = () => {\n log.blank()\n log.info('Development servers stopped.')\n process.exit(0)\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n\n try {\n await result\n } catch {\n // concurrently rejects when processes are killed\n }\n}\n","import path from 'node:path'\nimport fs from 'node:fs'\nimport { findProjectRoot, getBackendDir, getFrontendDir } from '../utils/paths.js'\nimport { exec, execPython } from '../utils/exec.js'\nimport { log, spinner } from '../utils/logger.js'\n\nexport async function sync() {\n let root: string\n try {\n root = findProjectRoot()\n } catch {\n log.error('Not inside a Blacksmith project. Run \"blacksmith init <name>\" first.')\n process.exit(1)\n }\n\n const backendDir = getBackendDir(root)\n const frontendDir = getFrontendDir(root)\n const s = spinner('Syncing OpenAPI schema to frontend...')\n\n try {\n // Generate schema offline using drf-spectacular management command\n const schemaPath = path.join(frontendDir, '_schema.yml')\n await execPython(['manage.py', 'spectacular', '--file', schemaPath], backendDir, true)\n\n // Temporarily update the openapi-ts config to use the local schema file\n const configPath = path.join(frontendDir, 'openapi-ts.config.ts')\n const configBackup = fs.readFileSync(configPath, 'utf-8')\n const configWithFile = configBackup.replace(\n /path:\\s*['\"]http[^'\"]+['\"]/,\n `path: './_schema.yml'`\n )\n fs.writeFileSync(configPath, configWithFile, 'utf-8')\n\n try {\n await exec(process.execPath, [path.join(frontendDir, 'node_modules', '.bin', 'openapi-ts')], {\n cwd: frontendDir,\n silent: true,\n })\n } finally {\n // Always restore the original config and clean up the schema file\n fs.writeFileSync(configPath, configBackup, 'utf-8')\n if (fs.existsSync(schemaPath)) fs.unlinkSync(schemaPath)\n }\n\n s.succeed('Frontend types, schemas, and hooks synced from OpenAPI spec')\n log.blank()\n log.step('Generated files in frontend/src/api/generated/:')\n log.step(' types.gen.ts → TypeScript interfaces')\n log.step(' zod.gen.ts → Zod validation schemas')\n log.step(' sdk.gen.ts → API client functions')\n log.step(' @tanstack/react-query.gen.ts → TanStack Query hooks')\n log.blank()\n } catch (error: any) {\n s.fail('Failed to sync OpenAPI schema')\n log.error(error.message || error)\n process.exit(1)\n }\n\n}\n","import path from 'node:path'\nimport fs from 'node:fs'\nimport { findProjectRoot, getBackendDir, getFrontendDir, getTemplatesDir } from '../utils/paths.js'\nimport { generateNames } from '../utils/names.js'\nimport { renderDirectory, appendAfterMarker, insertBeforeMarker } from '../utils/template.js'\nimport { exec, execPython } from '../utils/exec.js'\nimport { log, spinner } from '../utils/logger.js'\n\nexport async function makeResource(name: string) {\n let root: string\n try {\n root = findProjectRoot()\n } catch {\n log.error('Not inside a Blacksmith project. Run \"blacksmith init <name>\" first.')\n process.exit(1)\n }\n\n const names = generateNames(name)\n const backendDir = getBackendDir(root)\n const frontendDir = getFrontendDir(root)\n const templatesDir = getTemplatesDir()\n\n const backendAppDir = path.join(backendDir, 'apps', names.snakes)\n\n // Check if backend resource already exists\n if (fs.existsSync(backendAppDir)) {\n log.error(`Backend app \"${names.snakes}\" already exists.`)\n process.exit(1)\n }\n\n const frontendPageDir = path.join(frontendDir, 'src', 'pages', names.kebabs)\n\n if (fs.existsSync(frontendPageDir)) {\n log.error(`Frontend page \"${names.kebabs}\" already exists.`)\n process.exit(1)\n }\n\n const context = { ...names, projectName: name }\n\n // 1. Generate backend app\n const backendSpinner = spinner(`Creating backend app: apps/${names.snakes}/`)\n try {\n renderDirectory(\n path.join(templatesDir, 'resource', 'backend'),\n backendAppDir,\n context\n )\n backendSpinner.succeed(`Created backend/apps/${names.snakes}/`)\n } catch (error: any) {\n backendSpinner.fail('Failed to create backend app')\n log.error(error.message)\n process.exit(1)\n }\n\n // 2. Register app in settings\n const registerSpinner = spinner('Registering app in Django settings...')\n try {\n const settingsPath = path.join(backendDir, 'config', 'settings', 'base.py')\n appendAfterMarker(\n settingsPath,\n '# blacksmith:apps',\n ` 'apps.${names.snakes}',`\n )\n registerSpinner.succeed('Registered in INSTALLED_APPS')\n } catch (error: any) {\n registerSpinner.fail('Failed to register app in settings')\n log.error(error.message)\n process.exit(1)\n }\n\n // 3. Register URLs\n const urlSpinner = spinner('Registering API URLs...')\n try {\n const urlsPath = path.join(backendDir, 'config', 'urls.py')\n insertBeforeMarker(\n urlsPath,\n '# blacksmith:urls',\n ` path('api/${names.snakes}/', include('apps.${names.snakes}.urls')),`\n )\n urlSpinner.succeed('Registered API URLs')\n } catch (error: any) {\n urlSpinner.fail('Failed to register URLs')\n log.error(error.message)\n process.exit(1)\n }\n\n // 4. Run migrations\n const migrateSpinner = spinner('Running migrations...')\n try {\n await execPython(['manage.py', 'makemigrations', names.snakes], backendDir, true)\n await execPython(['manage.py', 'migrate'], backendDir, true)\n migrateSpinner.succeed('Migrations complete')\n } catch (error: any) {\n migrateSpinner.fail('Migration failed')\n log.error(error.message)\n process.exit(1)\n }\n\n // 5. Sync OpenAPI (generate schema offline, no running Django needed)\n const syncSpinner = spinner('Syncing OpenAPI schema...')\n try {\n const schemaPath = path.join(frontendDir, '_schema.yml')\n await execPython(['manage.py', 'spectacular', '--file', schemaPath], backendDir, true)\n\n const configPath = path.join(frontendDir, 'openapi-ts.config.ts')\n const configBackup = fs.readFileSync(configPath, 'utf-8')\n const configWithFile = configBackup.replace(\n /path:\\s*['\"]http[^'\"]+['\"]/,\n `path: './_schema.yml'`\n )\n fs.writeFileSync(configPath, configWithFile, 'utf-8')\n\n try {\n await exec(process.execPath, [path.join(frontendDir, 'node_modules', '.bin', 'openapi-ts')], {\n cwd: frontendDir,\n silent: true,\n })\n } finally {\n fs.writeFileSync(configPath, configBackup, 'utf-8')\n if (fs.existsSync(schemaPath)) fs.unlinkSync(schemaPath)\n }\n\n syncSpinner.succeed('Frontend types and hooks regenerated')\n } catch {\n syncSpinner.warn('Could not sync OpenAPI. Run \"blacksmith sync\" manually.')\n }\n\n // 6. Generate API hooks\n const apiHooksDir = path.join(frontendDir, 'src', 'api', 'hooks', names.kebabs)\n const apiHooksSpinner = spinner(`Creating API hooks: api/hooks/${names.kebabs}/`)\n try {\n renderDirectory(\n path.join(templatesDir, 'resource', 'api-hooks'),\n apiHooksDir,\n context\n )\n apiHooksSpinner.succeed(`Created frontend/src/api/hooks/${names.kebabs}/`)\n } catch (error: any) {\n apiHooksSpinner.fail('Failed to create API hooks')\n log.error(error.message)\n process.exit(1)\n }\n\n // 7. Generate frontend page\n const frontendSpinner = spinner(`Creating frontend page: pages/${names.kebabs}/`)\n try {\n renderDirectory(\n path.join(templatesDir, 'resource', 'pages'),\n frontendPageDir,\n context\n )\n frontendSpinner.succeed(`Created frontend/src/pages/${names.kebabs}/`)\n } catch (error: any) {\n frontendSpinner.fail('Failed to create frontend page')\n log.error(error.message)\n process.exit(1)\n }\n\n // 8. Register path in paths enum\n const pathSpinner = spinner('Registering route path...')\n try {\n const pathsFile = path.join(frontendDir, 'src', 'router', 'paths.ts')\n insertBeforeMarker(\n pathsFile,\n '// blacksmith:path',\n ` ${names.Names} = '/${names.kebabs}',`\n )\n pathSpinner.succeed('Registered route path')\n } catch {\n pathSpinner.warn('Could not auto-register path. Add it manually to frontend/src/router/paths.ts')\n }\n\n // 9. Register routes in frontend router\n const routeSpinner = spinner('Registering frontend routes...')\n try {\n const routesPath = path.join(frontendDir, 'src', 'router', 'routes.tsx')\n insertBeforeMarker(\n routesPath,\n '// blacksmith:import',\n `import { ${names.names}Routes } from '@/pages/${names.kebabs}'`\n )\n insertBeforeMarker(\n routesPath,\n '// blacksmith:routes',\n ` ...${names.names}Routes,`\n )\n routeSpinner.succeed('Registered frontend routes')\n } catch {\n routeSpinner.warn('Could not auto-register routes. Add them manually to frontend/src/router/routes.tsx')\n }\n\n // 10. Print summary\n log.blank()\n log.success(`Resource \"${names.Name}\" created successfully!`)\n log.blank()\n}\n","import { pascalCase, snakeCase, kebabCase, camelCase } from 'change-case'\nimport pluralize from 'pluralize'\n\nexport interface NameVariants {\n /** PascalCase singular: Post */\n Name: string\n /** PascalCase plural: Posts */\n Names: string\n /** camelCase singular: post */\n name: string\n /** camelCase plural: posts */\n names: string\n /** snake_case singular: post */\n snake: string\n /** snake_case plural: posts */\n snakes: string\n /** kebab-case singular: post */\n kebab: string\n /** kebab-case plural: posts */\n kebabs: string\n /** UPPER_SNAKE singular: POST */\n UPPER: string\n /** UPPER_SNAKE plural: POSTS */\n UPPERS: string\n}\n\n/**\n * Generate all name variants from a singular PascalCase name\n *\n * @example\n * generateNames('BlogPost')\n * // {\n * // Name: 'BlogPost', Names: 'BlogPosts',\n * // name: 'blogPost', names: 'blogPosts',\n * // snake: 'blog_post', snakes: 'blog_posts',\n * // kebab: 'blog-post', kebabs: 'blog-posts',\n * // UPPER: 'BLOG_POST', UPPERS: 'BLOG_POSTS',\n * // }\n */\nexport function generateNames(input: string): NameVariants {\n const singular = pascalCase(input)\n const plural = pluralize(singular)\n\n return {\n Name: singular,\n Names: plural,\n name: camelCase(singular),\n names: camelCase(plural),\n snake: snakeCase(singular),\n snakes: snakeCase(plural),\n kebab: kebabCase(singular),\n kebabs: kebabCase(plural),\n UPPER: snakeCase(singular).toUpperCase(),\n UPPERS: snakeCase(plural).toUpperCase(),\n }\n}\n","import { findProjectRoot, getBackendDir, getFrontendDir } from '../utils/paths.js'\nimport { exec, execPython } from '../utils/exec.js'\nimport { log, spinner } from '../utils/logger.js'\n\nexport async function build() {\n let root: string\n try {\n root = findProjectRoot()\n } catch {\n log.error('Not inside a Blacksmith project. Run \"blacksmith init <name>\" first.')\n process.exit(1)\n }\n\n const backendDir = getBackendDir(root)\n const frontendDir = getFrontendDir(root)\n\n // Build frontend\n const frontendSpinner = spinner('Building frontend...')\n try {\n await exec('npm', ['run', 'build'], { cwd: frontendDir, silent: true })\n frontendSpinner.succeed('Frontend built → frontend/dist/')\n } catch (error: any) {\n frontendSpinner.fail('Frontend build failed')\n log.error(error.message || error)\n process.exit(1)\n }\n\n // Collect static files\n const backendSpinner = spinner('Collecting static files...')\n try {\n await execPython(\n ['manage.py', 'collectstatic', '--noinput'],\n backendDir,\n true\n )\n backendSpinner.succeed('Static files collected')\n } catch (error: any) {\n backendSpinner.fail('Failed to collect static files')\n log.error(error.message || error)\n process.exit(1)\n }\n\n log.blank()\n log.success('Production build complete!')\n log.blank()\n log.step('Frontend assets: frontend/dist/')\n log.step('Backend ready for deployment')\n log.blank()\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport { findProjectRoot } from '../utils/paths.js'\nimport { log } from '../utils/logger.js'\n\nexport async function eject() {\n let root: string\n try {\n root = findProjectRoot()\n } catch {\n log.error('Not inside a Blacksmith project.')\n process.exit(1)\n }\n\n const configPath = path.join(root, 'blacksmith.config.json')\n\n if (fs.existsSync(configPath)) {\n fs.unlinkSync(configPath)\n }\n\n log.success('Blacksmith has been ejected.')\n log.blank()\n log.step('Your project is now a standard Django + React project.')\n log.step('All generated code remains in place and is fully owned by you.')\n log.step('The blacksmith CLI commands will no longer work in this directory.')\n log.blank()\n log.info('To continue development without Blacksmith:')\n log.step('Backend: cd backend && ./venv/bin/python manage.py runserver')\n log.step('Frontend: cd frontend && npm run dev')\n log.step('Codegen: cd frontend && npx openapi-ts')\n log.blank()\n}\n","import fs from 'node:fs'\nimport { findProjectRoot, loadConfig } from '../utils/paths.js'\nimport { setupAiDev } from './ai-setup.js'\nimport { log } from '../utils/logger.js'\n\nimport { projectOverviewSkill } from '../skills/project-overview.js'\nimport { djangoSkill } from '../skills/django.js'\nimport { djangoRestAdvancedSkill } from '../skills/django-rest-advanced.js'\nimport { apiDocumentationSkill } from '../skills/api-documentation.js'\nimport { reactSkill } from '../skills/react.js'\nimport { blacksmithUiReactSkill } from '../skills/blacksmith-ui-react.js'\nimport { blacksmithUiFormsSkill } from '../skills/blacksmith-ui-forms.js'\nimport { blacksmithUiAuthSkill } from '../skills/blacksmith-ui-auth.js'\nimport { blacksmithHooksSkill } from '../skills/blacksmith-hooks.js'\nimport { blacksmithCliSkill } from '../skills/blacksmith-cli.js'\nimport { frontendTestingSkill } from '../skills/frontend-testing.js'\nimport { cleanCodeSkill } from '../skills/clean-code.js'\nimport { aiGuidelinesSkill } from '../skills/ai-guidelines.js'\nimport type { Skill } from '../skills/types.js'\n\nconst allSkills: Skill[] = [\n projectOverviewSkill,\n djangoSkill,\n djangoRestAdvancedSkill,\n apiDocumentationSkill,\n reactSkill,\n blacksmithUiReactSkill,\n blacksmithUiFormsSkill,\n blacksmithUiAuthSkill,\n blacksmithHooksSkill,\n blacksmithCliSkill,\n frontendTestingSkill,\n cleanCodeSkill,\n aiGuidelinesSkill,\n]\n\ninterface SetupOptions {\n blacksmithUiSkill?: boolean\n}\n\nexport async function setupSkills(options: SetupOptions) {\n let root: string\n try {\n root = findProjectRoot()\n } catch {\n log.error('Not inside a Blacksmith project. Run \"blacksmith init <name>\" first.')\n process.exit(1)\n }\n\n const config = loadConfig(root)\n\n await setupAiDev({\n projectDir: root,\n projectName: config.name,\n includeBlacksmithUiSkill: options.blacksmithUiSkill !== false,\n })\n\n log.blank()\n log.success('AI skills generated:')\n log.step(' CLAUDE.md → project overview + guidelines')\n log.step(' .claude/skills/*/SKILL.md → detailed skill files')\n}\n\nexport function listSkills() {\n let root: string\n try {\n root = findProjectRoot()\n } catch {\n log.error('Not inside a Blacksmith project. Run \"blacksmith init <name>\" first.')\n process.exit(1)\n }\n\n const hasClaude = fs.existsSync(`${root}/CLAUDE.md`)\n const hasSkillsDir = fs.existsSync(`${root}/.claude/skills`)\n\n const inlineSkills = allSkills.filter((s) => !s.name)\n const fileSkills = allSkills.filter((s) => s.name)\n\n log.info('Inline skills (in CLAUDE.md):')\n for (const skill of inlineSkills) {\n log.step(` ${skill.id}`)\n }\n\n log.blank()\n log.info('File-based skills (in .claude/skills/):')\n for (const skill of fileSkills) {\n const exists = hasSkillsDir && fs.existsSync(`${root}/.claude/skills/${skill.id}/SKILL.md`)\n const status = exists ? '✓' : '✗'\n log.step(` ${status} ${skill.id}/SKILL.md — ${skill.name}`)\n }\n\n log.blank()\n if (hasClaude && hasSkillsDir) {\n log.success('AI skills are set up. Run \"blacksmith setup:ai\" to regenerate.')\n } else {\n log.info('Run \"blacksmith setup:ai\" to generate AI skills.')\n }\n}\n","import { findProjectRoot, getBackendDir } from '../utils/paths.js'\nimport { log } from '../utils/logger.js'\nimport { execPython } from '../utils/exec.js'\n\nexport async function backend(args: string[]) {\n let root: string\n try {\n root = findProjectRoot()\n } catch {\n log.error('Not inside a Blacksmith project. Run \"blacksmith init <name>\" first.')\n process.exit(1)\n }\n if (args.length === 0) {\n log.error('Please provide a Django management command.')\n log.step('Usage: blacksmith backend <command> [args...]')\n log.step('Example: blacksmith backend createsuperuser')\n process.exit(1)\n }\n\n const backendDir = getBackendDir(root)\n\n try {\n await execPython(['manage.py', ...args], backendDir)\n } catch {\n process.exit(1)\n }\n}\n","import { findProjectRoot, getFrontendDir } from '../utils/paths.js'\nimport { log } from '../utils/logger.js'\nimport { exec } from '../utils/exec.js'\n\nexport async function frontend(args: string[]) {\n let root: string\n try {\n root = findProjectRoot()\n } catch {\n log.error('Not inside a Blacksmith project. Run \"blacksmith init <name>\" first.')\n process.exit(1)\n }\n if (args.length === 0) {\n log.error('Please provide an npm command.')\n log.step('Usage: blacksmith frontend <command> [args...]')\n log.step('Example: blacksmith frontend install axios')\n process.exit(1)\n }\n\n const frontendDir = getFrontendDir(root)\n\n try {\n await exec('npm', args, { cwd: frontendDir })\n } catch {\n process.exit(1)\n }\n}\n"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","names":["path","fs","path","fs","__filename","__dirname","path","fs","path","fs","path","fs","path","path","path","fs","path","fs","path","fs","path","fs","fs","path","path","fs","fs","fs"]}