blacksmith-cli 0.1.8 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1200 -812
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/templates/frontend/src/api/hooks/.gitkeep +0 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../node_modules/tsup/assets/esm_shims.js","../node_modules/is-docker/index.js","../node_modules/is-inside-container/index.js","../node_modules/is-wsl/index.js","../node_modules/powershell-utils/index.js","../node_modules/wsl-utils/utilities.js","../node_modules/wsl-utils/index.js","../node_modules/define-lazy-prop/index.js","../node_modules/default-browser-id/index.js","../node_modules/run-applescript/index.js","../node_modules/bundle-name/index.js","../node_modules/default-browser/windows.js","../node_modules/default-browser/index.js","../node_modules/is-in-ssh/index.js","../node_modules/open/index.js","../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/chakra-ui-react.ts","../src/skills/chakra-ui-forms.ts","../src/skills/chakra-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/mcp-setup.ts","../src/commands/studio.ts","../src/commands/backend.ts","../src/commands/frontend.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","import fs from 'node:fs';\n\nlet isDockerCached;\n\nfunction hasDockerEnv() {\n\ttry {\n\t\tfs.statSync('/.dockerenv');\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction hasDockerCGroup() {\n\ttry {\n\t\treturn fs.readFileSync('/proc/self/cgroup', 'utf8').includes('docker');\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nexport default function isDocker() {\n\t// TODO: Use `??=` when targeting Node.js 16.\n\tif (isDockerCached === undefined) {\n\t\tisDockerCached = hasDockerEnv() || hasDockerCGroup();\n\t}\n\n\treturn isDockerCached;\n}\n","import fs from 'node:fs';\nimport isDocker from 'is-docker';\n\nlet cachedResult;\n\n// Podman detection\nconst hasContainerEnv = () => {\n\ttry {\n\t\tfs.statSync('/run/.containerenv');\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n};\n\nexport default function isInsideContainer() {\n\t// TODO: Use `??=` when targeting Node.js 16.\n\tif (cachedResult === undefined) {\n\t\tcachedResult = hasContainerEnv() || isDocker();\n\t}\n\n\treturn cachedResult;\n}\n","import process from 'node:process';\nimport os from 'node:os';\nimport fs from 'node:fs';\nimport isInsideContainer from 'is-inside-container';\n\nconst isWsl = () => {\n\tif (process.platform !== 'linux') {\n\t\treturn false;\n\t}\n\n\tif (os.release().toLowerCase().includes('microsoft')) {\n\t\tif (isInsideContainer()) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\ttry {\n\t\tif (fs.readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft')) {\n\t\t\treturn !isInsideContainer();\n\t\t}\n\t} catch {}\n\n\t// Fallback for custom kernels: check WSL-specific paths.\n\tif (\n\t\tfs.existsSync('/proc/sys/fs/binfmt_misc/WSLInterop')\n\t\t|| fs.existsSync('/run/WSL')\n\t) {\n\t\treturn !isInsideContainer();\n\t}\n\n\treturn false;\n};\n\nexport default process.env.__IS_WSL_TEST__ ? isWsl : isWsl();\n","import process from 'node:process';\nimport {Buffer} from 'node:buffer';\nimport {promisify} from 'node:util';\nimport childProcess from 'node:child_process';\nimport fs, {constants as fsConstants} from 'node:fs/promises';\n\nconst execFile = promisify(childProcess.execFile);\n\nexport const powerShellPath = () => `${process.env.SYSTEMROOT || process.env.windir || String.raw`C:\\Windows`}\\\\System32\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe`;\n\n// Cache for PowerShell accessibility check\nlet canAccessCache;\n\nexport const canAccessPowerShell = async () => {\n\tcanAccessCache ??= (async () => {\n\t\ttry {\n\t\t\tawait fs.access(powerShellPath(), fsConstants.X_OK);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t})();\n\n\treturn canAccessCache;\n};\n\nexport const executePowerShell = async (command, options = {}) => {\n\tconst {\n\t\tpowerShellPath: psPath,\n\t\t...execFileOptions\n\t} = options;\n\n\tconst encodedCommand = executePowerShell.encodeCommand(command);\n\n\treturn execFile(\n\t\tpsPath ?? powerShellPath(),\n\t\t[\n\t\t\t...executePowerShell.argumentsPrefix,\n\t\t\tencodedCommand,\n\t\t],\n\t\t{\n\t\t\tencoding: 'utf8',\n\t\t\t...execFileOptions,\n\t\t},\n\t);\n};\n\nexecutePowerShell.argumentsPrefix = [\n\t'-NoProfile',\n\t'-NonInteractive',\n\t'-ExecutionPolicy',\n\t'Bypass',\n\t'-EncodedCommand',\n];\n\nexecutePowerShell.encodeCommand = command => Buffer.from(command, 'utf16le').toString('base64');\n\nexecutePowerShell.escapeArgument = value => `'${String(value).replaceAll('\\'', '\\'\\'')}'`;\n","export function parseMountPointFromConfig(content) {\n\tfor (const line of content.split('\\n')) {\n\t\t// Skip comment lines\n\t\tif (/^\\s*#/.test(line)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Match root at start of line (after optional whitespace)\n\t\tconst match = /^\\s*root\\s*=\\s*(?<mountPoint>\"[^\"]*\"|'[^']*'|[^#]*)/.exec(line);\n\t\tif (!match) {\n\t\t\tcontinue;\n\t\t}\n\n\t\treturn match.groups.mountPoint\n\t\t\t.trim()\n\t\t\t// Strip surrounding quotes\n\t\t\t.replaceAll(/^[\"']|[\"']$/g, '');\n\t}\n}\n","import {promisify} from 'node:util';\nimport childProcess from 'node:child_process';\nimport fs, {constants as fsConstants} from 'node:fs/promises';\nimport isWsl from 'is-wsl';\nimport {powerShellPath as windowsPowerShellPath, executePowerShell} from 'powershell-utils';\nimport {parseMountPointFromConfig} from './utilities.js';\n\nconst execFile = promisify(childProcess.execFile);\n\nexport const wslDrivesMountPoint = (() => {\n\t// Default value for \"root\" param\n\t// according to https://docs.microsoft.com/en-us/windows/wsl/wsl-config\n\tconst defaultMountPoint = '/mnt/';\n\n\tlet mountPoint;\n\n\treturn async function () {\n\t\tif (mountPoint) {\n\t\t\t// Return memoized mount point value\n\t\t\treturn mountPoint;\n\t\t}\n\n\t\tconst configFilePath = '/etc/wsl.conf';\n\n\t\tlet isConfigFileExists = false;\n\t\ttry {\n\t\t\tawait fs.access(configFilePath, fsConstants.F_OK);\n\t\t\tisConfigFileExists = true;\n\t\t} catch {}\n\n\t\tif (!isConfigFileExists) {\n\t\t\treturn defaultMountPoint;\n\t\t}\n\n\t\tconst configContent = await fs.readFile(configFilePath, {encoding: 'utf8'});\n\t\tconst parsedMountPoint = parseMountPointFromConfig(configContent);\n\n\t\tif (parsedMountPoint === undefined) {\n\t\t\treturn defaultMountPoint;\n\t\t}\n\n\t\tmountPoint = parsedMountPoint;\n\t\tmountPoint = mountPoint.endsWith('/') ? mountPoint : `${mountPoint}/`;\n\n\t\treturn mountPoint;\n\t};\n})();\n\nexport const powerShellPathFromWsl = async () => {\n\tconst mountPoint = await wslDrivesMountPoint();\n\treturn `${mountPoint}c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`;\n};\n\nexport const powerShellPath = isWsl ? powerShellPathFromWsl : windowsPowerShellPath;\n\n// Cache for PowerShell accessibility check\nlet canAccessPowerShellPromise;\n\nexport const canAccessPowerShell = async () => {\n\tcanAccessPowerShellPromise ??= (async () => {\n\t\ttry {\n\t\t\tconst psPath = await powerShellPath();\n\t\t\tawait fs.access(psPath, fsConstants.X_OK);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\t// PowerShell is not accessible (either doesn't exist, no execute permission, or other error)\n\t\t\treturn false;\n\t\t}\n\t})();\n\n\treturn canAccessPowerShellPromise;\n};\n\nexport const wslDefaultBrowser = async () => {\n\tconst psPath = await powerShellPath();\n\tconst command = String.raw`(Get-ItemProperty -Path \"HKCU:\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice\").ProgId`;\n\n\tconst {stdout} = await executePowerShell(command, {powerShellPath: psPath});\n\n\treturn stdout.trim();\n};\n\nexport const convertWslPathToWindows = async path => {\n\t// Don't convert URLs\n\tif (/^[a-z]+:\\/\\//i.test(path)) {\n\t\treturn path;\n\t}\n\n\ttry {\n\t\tconst {stdout} = await execFile('wslpath', ['-aw', path], {encoding: 'utf8'});\n\t\treturn stdout.trim();\n\t} catch {\n\t\t// If wslpath fails, return the original path\n\t\treturn path;\n\t}\n};\n\nexport {default as isWsl} from 'is-wsl';\n","export default function defineLazyProperty(object, propertyName, valueGetter) {\n\tconst define = value => Object.defineProperty(object, propertyName, {value, enumerable: true, writable: true});\n\n\tObject.defineProperty(object, propertyName, {\n\t\tconfigurable: true,\n\t\tenumerable: true,\n\t\tget() {\n\t\t\tconst result = valueGetter();\n\t\t\tdefine(result);\n\t\t\treturn result;\n\t\t},\n\t\tset(value) {\n\t\t\tdefine(value);\n\t\t}\n\t});\n\n\treturn object;\n}\n","import {promisify} from 'node:util';\nimport process from 'node:process';\nimport {execFile} from 'node:child_process';\n\nconst execFileAsync = promisify(execFile);\n\nexport default async function defaultBrowserId() {\n\tif (process.platform !== 'darwin') {\n\t\tthrow new Error('macOS only');\n\t}\n\n\tconst {stdout} = await execFileAsync('defaults', ['read', 'com.apple.LaunchServices/com.apple.launchservices.secure', 'LSHandlers']);\n\n\t// `(?!-)` is to prevent matching `LSHandlerRoleAll = \"-\";`.\n\tconst match = /LSHandlerRoleAll = \"(?!-)(?<id>[^\"]+?)\";\\s+?LSHandlerURLScheme = (?:http|https);/.exec(stdout);\n\n\tconst browserId = match?.groups.id ?? 'com.apple.Safari';\n\n\t// Correct the case for Safari's bundle identifier\n\tif (browserId === 'com.apple.safari') {\n\t\treturn 'com.apple.Safari';\n\t}\n\n\treturn browserId;\n}\n","import process from 'node:process';\nimport {promisify} from 'node:util';\nimport {execFile, execFileSync} from 'node:child_process';\n\nconst execFileAsync = promisify(execFile);\n\nexport async function runAppleScript(script, {humanReadableOutput = true, signal} = {}) {\n\tif (process.platform !== 'darwin') {\n\t\tthrow new Error('macOS only');\n\t}\n\n\tconst outputArguments = humanReadableOutput ? [] : ['-ss'];\n\n\tconst execOptions = {};\n\tif (signal) {\n\t\texecOptions.signal = signal;\n\t}\n\n\tconst {stdout} = await execFileAsync('osascript', ['-e', script, outputArguments], execOptions);\n\treturn stdout.trim();\n}\n\nexport function runAppleScriptSync(script, {humanReadableOutput = true} = {}) {\n\tif (process.platform !== 'darwin') {\n\t\tthrow new Error('macOS only');\n\t}\n\n\tconst outputArguments = humanReadableOutput ? [] : ['-ss'];\n\n\tconst stdout = execFileSync('osascript', ['-e', script, ...outputArguments], {\n\t\tencoding: 'utf8',\n\t\tstdio: ['ignore', 'pipe', 'ignore'],\n\t\ttimeout: 500,\n\t});\n\n\treturn stdout.trim();\n}\n","import {runAppleScript} from 'run-applescript';\n\nexport default async function bundleName(bundleId) {\n\treturn runAppleScript(`tell application \"Finder\" to set app_path to application file id \"${bundleId}\" as string\\ntell application \"System Events\" to get value of property list item \"CFBundleName\" of property list file (app_path & \":Contents:Info.plist\")`);\n}\n","import {promisify} from 'node:util';\nimport {execFile} from 'node:child_process';\n\nconst execFileAsync = promisify(execFile);\n\n// TODO: Fix the casing of bundle identifiers in the next major version.\n\n// Windows doesn't have browser IDs in the same way macOS/Linux does so we give fake\n// ones that look real and match the macOS/Linux versions for cross-platform apps.\nconst windowsBrowserProgIds = {\n\tMSEdgeHTM: {name: 'Edge', id: 'com.microsoft.edge'}, // The missing `L` is correct.\n\tMSEdgeBHTML: {name: 'Edge Beta', id: 'com.microsoft.edge.beta'},\n\tMSEdgeDHTML: {name: 'Edge Dev', id: 'com.microsoft.edge.dev'},\n\tAppXq0fevzme2pys62n3e0fbqa7peapykr8v: {name: 'Edge', id: 'com.microsoft.edge.old'},\n\tChromeHTML: {name: 'Chrome', id: 'com.google.chrome'},\n\tChromeBHTML: {name: 'Chrome Beta', id: 'com.google.chrome.beta'},\n\tChromeDHTML: {name: 'Chrome Dev', id: 'com.google.chrome.dev'},\n\tChromiumHTM: {name: 'Chromium', id: 'org.chromium.Chromium'},\n\tBraveHTML: {name: 'Brave', id: 'com.brave.Browser'},\n\tBraveBHTML: {name: 'Brave Beta', id: 'com.brave.Browser.beta'},\n\tBraveDHTML: {name: 'Brave Dev', id: 'com.brave.Browser.dev'},\n\tBraveSSHTM: {name: 'Brave Nightly', id: 'com.brave.Browser.nightly'},\n\tFirefoxURL: {name: 'Firefox', id: 'org.mozilla.firefox'},\n\tOperaStable: {name: 'Opera', id: 'com.operasoftware.Opera'},\n\tVivaldiHTM: {name: 'Vivaldi', id: 'com.vivaldi.Vivaldi'},\n\t'IE.HTTP': {name: 'Internet Explorer', id: 'com.microsoft.ie'},\n};\n\nexport const _windowsBrowserProgIdMap = new Map(Object.entries(windowsBrowserProgIds));\n\nexport class UnknownBrowserError extends Error {}\n\nexport default async function defaultBrowser(_execFileAsync = execFileAsync) {\n\tconst {stdout} = await _execFileAsync('reg', [\n\t\t'QUERY',\n\t\t' HKEY_CURRENT_USER\\\\Software\\\\Microsoft\\\\Windows\\\\Shell\\\\Associations\\\\UrlAssociations\\\\http\\\\UserChoice',\n\t\t'/v',\n\t\t'ProgId',\n\t]);\n\n\tconst match = /ProgId\\s*REG_SZ\\s*(?<id>\\S+)/.exec(stdout);\n\tif (!match) {\n\t\tthrow new UnknownBrowserError(`Cannot find Windows browser in stdout: ${JSON.stringify(stdout)}`);\n\t}\n\n\tconst {id} = match.groups;\n\n\t// Windows can append a hash suffix to ProgIds using a dot or hyphen\n\t// (e.g., `ChromeHTML.ABC123`, `FirefoxURL-6F193CCC56814779`).\n\t// Try exact match first, then try without the suffix.\n\tconst dotIndex = id.lastIndexOf('.');\n\tconst hyphenIndex = id.lastIndexOf('-');\n\tconst baseIdByDot = dotIndex === -1 ? undefined : id.slice(0, dotIndex);\n\tconst baseIdByHyphen = hyphenIndex === -1 ? undefined : id.slice(0, hyphenIndex);\n\n\treturn windowsBrowserProgIds[id] ?? windowsBrowserProgIds[baseIdByDot] ?? windowsBrowserProgIds[baseIdByHyphen] ?? {name: id, id};\n}\n","import {promisify} from 'node:util';\nimport process from 'node:process';\nimport {execFile} from 'node:child_process';\nimport defaultBrowserId from 'default-browser-id';\nimport bundleName from 'bundle-name';\nimport windows from './windows.js';\n\nexport {_windowsBrowserProgIdMap} from './windows.js';\n\nconst execFileAsync = promisify(execFile);\n\n// Inlined: https://github.com/sindresorhus/titleize/blob/main/index.js\nconst titleize = string => string.toLowerCase().replaceAll(/(?:^|\\s|-)\\S/g, x => x.toUpperCase());\n\nexport default async function defaultBrowser() {\n\tif (process.platform === 'darwin') {\n\t\tconst id = await defaultBrowserId();\n\t\tconst name = await bundleName(id);\n\t\treturn {name, id};\n\t}\n\n\tif (process.platform === 'linux') {\n\t\tconst {stdout} = await execFileAsync('xdg-mime', ['query', 'default', 'x-scheme-handler/http']);\n\t\tconst id = stdout.trim();\n\t\tconst name = titleize(id.replace(/.desktop$/, '').replace('-', ' '));\n\t\treturn {name, id};\n\t}\n\n\tif (process.platform === 'win32') {\n\t\treturn windows();\n\t}\n\n\tthrow new Error('Only macOS, Linux, and Windows are supported');\n}\n","import process from 'node:process';\n\nconst isInSsh = Boolean(process.env.SSH_CONNECTION\n\t|| process.env.SSH_CLIENT\n\t|| process.env.SSH_TTY);\n\nexport default isInSsh;\n","import process from 'node:process';\nimport path from 'node:path';\nimport {fileURLToPath} from 'node:url';\nimport childProcess from 'node:child_process';\nimport fs, {constants as fsConstants} from 'node:fs/promises';\nimport {\n\tisWsl,\n\tpowerShellPath,\n\tconvertWslPathToWindows,\n\tcanAccessPowerShell,\n\twslDefaultBrowser,\n} from 'wsl-utils';\nimport {executePowerShell} from 'powershell-utils';\nimport defineLazyProperty from 'define-lazy-prop';\nimport defaultBrowser, {_windowsBrowserProgIdMap} from 'default-browser';\nimport isInsideContainer from 'is-inside-container';\nimport isInSsh from 'is-in-ssh';\n\nconst fallbackAttemptSymbol = Symbol('fallbackAttempt');\n\n// Path to included `xdg-open`.\nconst __dirname = import.meta.url ? path.dirname(fileURLToPath(import.meta.url)) : '';\nconst localXdgOpenPath = path.join(__dirname, 'xdg-open');\n\nconst {platform, arch} = process;\n\nconst tryEachApp = async (apps, opener) => {\n\tif (apps.length === 0) {\n\t\t// No app was provided\n\t\treturn;\n\t}\n\n\tconst errors = [];\n\n\tfor (const app of apps) {\n\t\ttry {\n\t\t\treturn await opener(app); // eslint-disable-line no-await-in-loop\n\t\t} catch (error) {\n\t\t\terrors.push(error);\n\t\t}\n\t}\n\n\tthrow new AggregateError(errors, 'Failed to open in all supported apps');\n};\n\n// eslint-disable-next-line complexity\nconst baseOpen = async options => {\n\toptions = {\n\t\twait: false,\n\t\tbackground: false,\n\t\tnewInstance: false,\n\t\tallowNonzeroExitCode: false,\n\t\t...options,\n\t};\n\n\tconst isFallbackAttempt = options[fallbackAttemptSymbol] === true;\n\tdelete options[fallbackAttemptSymbol];\n\n\tif (Array.isArray(options.app)) {\n\t\treturn tryEachApp(options.app, singleApp => baseOpen({\n\t\t\t...options,\n\t\t\tapp: singleApp,\n\t\t\t[fallbackAttemptSymbol]: true,\n\t\t}));\n\t}\n\n\tlet {name: app, arguments: appArguments = []} = options.app ?? {};\n\tappArguments = [...appArguments];\n\n\tif (Array.isArray(app)) {\n\t\treturn tryEachApp(app, appName => baseOpen({\n\t\t\t...options,\n\t\t\tapp: {\n\t\t\t\tname: appName,\n\t\t\t\targuments: appArguments,\n\t\t\t},\n\t\t\t[fallbackAttemptSymbol]: true,\n\t\t}));\n\t}\n\n\tif (app === 'browser' || app === 'browserPrivate') {\n\t\t// IDs from default-browser for macOS and windows are the same.\n\t\t// IDs are lowercased to increase chances of a match.\n\t\tconst ids = {\n\t\t\t'com.google.chrome': 'chrome',\n\t\t\t'google-chrome.desktop': 'chrome',\n\t\t\t'com.brave.browser': 'brave',\n\t\t\t'org.mozilla.firefox': 'firefox',\n\t\t\t'firefox.desktop': 'firefox',\n\t\t\t'com.microsoft.msedge': 'edge',\n\t\t\t'com.microsoft.edge': 'edge',\n\t\t\t'com.microsoft.edgemac': 'edge',\n\t\t\t'microsoft-edge.desktop': 'edge',\n\t\t\t'com.apple.safari': 'safari',\n\t\t};\n\n\t\t// Incognito flags for each browser in `apps`.\n\t\tconst flags = {\n\t\t\tchrome: '--incognito',\n\t\t\tbrave: '--incognito',\n\t\t\tfirefox: '--private-window',\n\t\t\tedge: '--inPrivate',\n\t\t\t// Safari doesn't support private mode via command line\n\t\t};\n\n\t\tlet browser;\n\t\tif (isWsl) {\n\t\t\tconst progId = await wslDefaultBrowser();\n\t\t\tconst browserInfo = _windowsBrowserProgIdMap.get(progId);\n\t\t\tbrowser = browserInfo ?? {};\n\t\t} else {\n\t\t\tbrowser = await defaultBrowser();\n\t\t}\n\n\t\tif (browser.id in ids) {\n\t\t\tconst browserName = ids[browser.id.toLowerCase()];\n\n\t\t\tif (app === 'browserPrivate') {\n\t\t\t\t// Safari doesn't support private mode via command line\n\t\t\t\tif (browserName === 'safari') {\n\t\t\t\t\tthrow new Error('Safari doesn\\'t support opening in private mode via command line');\n\t\t\t\t}\n\n\t\t\t\tappArguments.push(flags[browserName]);\n\t\t\t}\n\n\t\t\treturn baseOpen({\n\t\t\t\t...options,\n\t\t\t\tapp: {\n\t\t\t\t\tname: apps[browserName],\n\t\t\t\t\targuments: appArguments,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tthrow new Error(`${browser.name} is not supported as a default browser`);\n\t}\n\n\tlet command;\n\tconst cliArguments = [];\n\tconst childProcessOptions = {};\n\n\t// Determine if we should use Windows/PowerShell behavior in WSL.\n\t// We only use Windows integration if PowerShell is actually accessible.\n\t// This allows the package to work in sandboxed WSL environments where Windows access is restricted.\n\tlet shouldUseWindowsInWsl = false;\n\tif (isWsl && !isInsideContainer() && !isInSsh && !app) {\n\t\tshouldUseWindowsInWsl = await canAccessPowerShell();\n\t}\n\n\tif (platform === 'darwin') {\n\t\tcommand = 'open';\n\n\t\tif (options.wait) {\n\t\t\tcliArguments.push('--wait-apps');\n\t\t}\n\n\t\tif (options.background) {\n\t\t\tcliArguments.push('--background');\n\t\t}\n\n\t\tif (options.newInstance) {\n\t\t\tcliArguments.push('--new');\n\t\t}\n\n\t\tif (app) {\n\t\t\tcliArguments.push('-a', app);\n\t\t}\n\t} else if (platform === 'win32' || shouldUseWindowsInWsl) {\n\t\tcommand = await powerShellPath();\n\n\t\tcliArguments.push(...executePowerShell.argumentsPrefix);\n\n\t\tif (!isWsl) {\n\t\t\tchildProcessOptions.windowsVerbatimArguments = true;\n\t\t}\n\n\t\t// Convert WSL Linux paths to Windows paths\n\t\tif (isWsl && options.target) {\n\t\t\toptions.target = await convertWslPathToWindows(options.target);\n\t\t}\n\n\t\t// Suppress PowerShell progress messages that are written to stderr\n\t\tconst encodedArguments = ['$ProgressPreference = \\'SilentlyContinue\\';', 'Start'];\n\n\t\tif (options.wait) {\n\t\t\tencodedArguments.push('-Wait');\n\t\t}\n\n\t\tif (app) {\n\t\t\tencodedArguments.push(executePowerShell.escapeArgument(app));\n\t\t\tif (options.target) {\n\t\t\t\tappArguments.push(options.target);\n\t\t\t}\n\t\t} else if (options.target) {\n\t\t\tencodedArguments.push(executePowerShell.escapeArgument(options.target));\n\t\t}\n\n\t\tif (appArguments.length > 0) {\n\t\t\tappArguments = appArguments.map(argument => executePowerShell.escapeArgument(argument));\n\t\t\tencodedArguments.push('-ArgumentList', appArguments.join(','));\n\t\t}\n\n\t\t// Using Base64-encoded command, accepted by PowerShell, to allow special characters.\n\t\toptions.target = executePowerShell.encodeCommand(encodedArguments.join(' '));\n\n\t\tif (!options.wait) {\n\t\t\t// PowerShell will keep the parent process alive unless stdio is ignored.\n\t\t\tchildProcessOptions.stdio = 'ignore';\n\t\t}\n\t} else {\n\t\tif (app) {\n\t\t\tcommand = app;\n\t\t} else {\n\t\t\t// When bundled by Webpack, there's no actual package file path and no local `xdg-open`.\n\t\t\tconst isBundled = !__dirname || __dirname === '/';\n\n\t\t\t// Check if local `xdg-open` exists and is executable.\n\t\t\tlet exeLocalXdgOpen = false;\n\t\t\ttry {\n\t\t\t\tawait fs.access(localXdgOpenPath, fsConstants.X_OK);\n\t\t\t\texeLocalXdgOpen = true;\n\t\t\t} catch {}\n\n\t\t\tconst useSystemXdgOpen = process.versions.electron\n\t\t\t\t?? (platform === 'android' || isBundled || !exeLocalXdgOpen);\n\t\t\tcommand = useSystemXdgOpen ? 'xdg-open' : localXdgOpenPath;\n\t\t}\n\n\t\tif (appArguments.length > 0) {\n\t\t\tcliArguments.push(...appArguments);\n\t\t}\n\n\t\tif (!options.wait) {\n\t\t\t// `xdg-open` will block the process unless stdio is ignored\n\t\t\t// and it's detached from the parent even if it's unref'd.\n\t\t\tchildProcessOptions.stdio = 'ignore';\n\t\t\tchildProcessOptions.detached = true;\n\t\t}\n\t}\n\n\tif (platform === 'darwin' && appArguments.length > 0) {\n\t\tcliArguments.push('--args', ...appArguments);\n\t}\n\n\t// IMPORTANT: On macOS, the target MUST come AFTER '--args'.\n\t// When using --args, ALL following arguments are passed to the app.\n\t// Example: open -a \"chrome\" --args --incognito https://site.com\n\t// This passes BOTH --incognito AND https://site.com to Chrome.\n\t// Without this order, Chrome won't open in incognito. See #332.\n\tif (options.target) {\n\t\tcliArguments.push(options.target);\n\t}\n\n\tconst subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);\n\n\tif (options.wait) {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tsubprocess.once('error', reject);\n\n\t\t\tsubprocess.once('close', exitCode => {\n\t\t\t\tif (!options.allowNonzeroExitCode && exitCode !== 0) {\n\t\t\t\t\treject(new Error(`Exited with code ${exitCode}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve(subprocess);\n\t\t\t});\n\t\t});\n\t}\n\n\t// When we're in a fallback attempt, we need to detect launch failures before trying the next app.\n\t// Wait for the close event to check the exit code before unreffing.\n\t// The launcher (open/xdg-open/PowerShell) exits quickly (~10-30ms) even on success.\n\tif (isFallbackAttempt) {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tsubprocess.once('error', reject);\n\n\t\t\tsubprocess.once('spawn', () => {\n\t\t\t\t// Keep error handler active for post-spawn errors\n\t\t\t\tsubprocess.once('close', exitCode => {\n\t\t\t\t\tsubprocess.off('error', reject);\n\n\t\t\t\t\tif (exitCode !== 0) {\n\t\t\t\t\t\treject(new Error(`Exited with code ${exitCode}`));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tsubprocess.unref();\n\t\t\t\t\tresolve(subprocess);\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\t}\n\n\tsubprocess.unref();\n\n\t// Handle spawn errors before the caller can attach listeners.\n\t// This prevents unhandled error events from crashing the process.\n\treturn new Promise((resolve, reject) => {\n\t\tsubprocess.once('error', reject);\n\n\t\t// Wait for the subprocess to spawn before resolving.\n\t\t// This ensures the process is established before the caller continues,\n\t\t// preventing issues when process.exit() is called immediately after.\n\t\tsubprocess.once('spawn', () => {\n\t\t\tsubprocess.off('error', reject);\n\t\t\tresolve(subprocess);\n\t\t});\n\t});\n};\n\nconst open = (target, options) => {\n\tif (typeof target !== 'string') {\n\t\tthrow new TypeError('Expected a `target`');\n\t}\n\n\treturn baseOpen({\n\t\t...options,\n\t\ttarget,\n\t});\n};\n\nexport const openApp = (name, options) => {\n\tif (typeof name !== 'string' && !Array.isArray(name)) {\n\t\tthrow new TypeError('Expected a valid `name`');\n\t}\n\n\tconst {arguments: appArguments = []} = options ?? {};\n\tif (appArguments !== undefined && appArguments !== null && !Array.isArray(appArguments)) {\n\t\tthrow new TypeError('Expected `appArguments` as Array type');\n\t}\n\n\treturn baseOpen({\n\t\t...options,\n\t\tapp: {\n\t\t\tname,\n\t\t\targuments: appArguments,\n\t\t},\n\t});\n};\n\nfunction detectArchBinary(binary) {\n\tif (typeof binary === 'string' || Array.isArray(binary)) {\n\t\treturn binary;\n\t}\n\n\tconst {[arch]: archBinary} = binary;\n\n\tif (!archBinary) {\n\t\tthrow new Error(`${arch} is not supported`);\n\t}\n\n\treturn archBinary;\n}\n\nfunction detectPlatformBinary({[platform]: platformBinary}, {wsl} = {}) {\n\tif (wsl && isWsl) {\n\t\treturn detectArchBinary(wsl);\n\t}\n\n\tif (!platformBinary) {\n\t\tthrow new Error(`${platform} is not supported`);\n\t}\n\n\treturn detectArchBinary(platformBinary);\n}\n\nexport const apps = {\n\tbrowser: 'browser',\n\tbrowserPrivate: 'browserPrivate',\n};\n\ndefineLazyProperty(apps, 'chrome', () => detectPlatformBinary({\n\tdarwin: 'google chrome',\n\twin32: 'chrome',\n\t// `chromium-browser` is the older deb package name used by Ubuntu/Debian before snap.\n\tlinux: ['google-chrome', 'google-chrome-stable', 'chromium', 'chromium-browser'],\n}, {\n\twsl: {\n\t\tia32: '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe',\n\t\tx64: ['/mnt/c/Program Files/Google/Chrome/Application/chrome.exe', '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe'],\n\t},\n}));\n\ndefineLazyProperty(apps, 'brave', () => detectPlatformBinary({\n\tdarwin: 'brave browser',\n\twin32: 'brave',\n\tlinux: ['brave-browser', 'brave'],\n}, {\n\twsl: {\n\t\tia32: '/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe',\n\t\tx64: ['/mnt/c/Program Files/BraveSoftware/Brave-Browser/Application/brave.exe', '/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe'],\n\t},\n}));\n\ndefineLazyProperty(apps, 'firefox', () => detectPlatformBinary({\n\tdarwin: 'firefox',\n\twin32: String.raw`C:\\Program Files\\Mozilla Firefox\\firefox.exe`,\n\tlinux: 'firefox',\n}, {\n\twsl: '/mnt/c/Program Files/Mozilla Firefox/firefox.exe',\n}));\n\ndefineLazyProperty(apps, 'edge', () => detectPlatformBinary({\n\tdarwin: 'microsoft edge',\n\twin32: 'msedge',\n\tlinux: ['microsoft-edge', 'microsoft-edge-dev'],\n}, {\n\twsl: '/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',\n}));\n\ndefineLazyProperty(apps, 'safari', () => detectPlatformBinary({\n\tdarwin: 'Safari',\n}));\n\nexport default open;\n","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 { setupMcp } from './commands/mcp-setup.js'\nimport { studio } from './commands/studio.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-chakra-ui-skill', 'Disable Chakra 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-chakra-ui-skill', 'Exclude Chakra UI skill')\n .action(setupSkills)\n\nprogram\n .command('setup:mcp')\n .description('Configure MCP servers for Claude Code AI integration')\n .action(setupMcp)\n\nprogram\n .command('studio')\n .description('Launch Blacksmith Studio — web UI for Claude Code')\n .option('-p, --port <port>', 'Port for the Studio server (default: 3939)')\n .action(studio)\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 chakraUiSkill?: 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 includeChakraUiSkill: options.chakraUiSkill !== 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 { chakraUiReactSkill } from '../skills/chakra-ui-react.js'\nimport { chakraUiFormsSkill } from '../skills/chakra-ui-forms.js'\nimport { chakraUiAuthSkill } from '../skills/chakra-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 includeChakraUiSkill: boolean\n}\n\nexport async function setupAiDev({ projectDir, projectName, includeChakraUiSkill }: 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 (includeChakraUiSkill) {\n skills.push(chakraUiReactSkill)\n skills.push(chakraUiFormsSkill)\n skills.push(chakraUiAuthSkill)\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 \\`@chakra-ui/react\\` for ALL UI\n- **Layout**: Use \\`VStack\\`, \\`HStack\\`, \\`Flex\\`, \\`SimpleGrid\\`, \\`Box\\`, \\`Container\\` — NEVER \\`<div className=\"flex ...\">\\` or \\`<div className=\"grid ...\">\\`\n- **Typography**: Use \\`Heading\\` and \\`Text\\` — NEVER raw \\`<h1>\\`–\\`<h6>\\`, \\`<p>\\`, or \\`<span>\\` with text classes\n- **Separators**: Use \\`Divider\\` — NEVER \\`<hr>\\`\n- **Everything else**: \\`Button\\`, \\`Card\\`, \\`Badge\\`, \\`Input\\`, \\`Table\\`, \\`Modal\\`, \\`Alert\\`, \\`Skeleton\\`, \\`Stat\\`, etc.\n- See the \\`chakra-ui-react\\` skill for the full 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- Chakra UI v2 for component library and theming\n- Tailwind CSS for additional 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 \\`@chakra-ui/react\\` components** — see the \\`chakra-ui-react\\` skill for the full component list\n- Use \\`VStack\\`, \\`HStack\\`, \\`Flex\\`, \\`SimpleGrid\\`, \\`Box\\` for layout — never raw \\`<div>\\` with flex/grid classes\n- Use \\`Heading\\` and \\`Text\\` for headings and text — never raw \\`<h1>\\`–\\`<h6>\\` or \\`<p>\\`\n- Use \\`Divider\\` instead of \\`<hr>\\`\n- Use \\`Stat\\`, \\`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 Chakra UI style props as the primary styling approach\n- Use Tailwind CSS utility classes for additional styling needs\n- Theming via Chakra UI \\`extendTheme()\\` and design tokens\n- Color mode is supported via Chakra UI \\`useColorMode()\\` hook\n- Use responsive props (\\`{{ base: ..., md: ..., lg: ... }}\\`) for responsive layouts\n- Avoid inline \\`style\\` attributes — use Chakra style props or 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, useToast)\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 @chakra-ui/react layout\nimport { VStack, SimpleGrid, Divider } from '@chakra-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 <VStack spacing={6} align=\"stretch\">\n <StatsCards stats={stats} isLoading={isLoading} />\n <Divider />\n <SimpleGrid columns={{ base: 1, lg: 3 }} spacing={6}>\n <RecentActivity items={activity} isLoading={isLoading} gridColumn={{ lg: 'span 2' }} />\n <QuickActions />\n </SimpleGrid>\n </VStack>\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 { VStack } from '@chakra-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 <VStack spacing={4} align=\"stretch\">\n <OrdersToolbar search={search} onSearchChange={setSearch} />\n <OrdersTable orders={orders} isLoading={isLoading} onDelete={(id) => deleteOrder.mutate({ path: { id } })} />\n </VStack>\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 chakraUiReactSkill: Skill = {\n id: 'chakra-ui-react',\n name: 'Chakra UI React',\n description: 'Core UI component library — Chakra UI v2 components for layout, typography, inputs, data display, overlays, feedback, media, and navigation.',\n\n render(_ctx: SkillContext): string {\n return `## Chakra UI React — Core UI Components\n\n> **CRITICAL RULE: Every UI element MUST be built using \\`@chakra-ui/react\\` components — including layout and typography.**\n> Do NOT use raw HTML elements when a Chakra UI component exists for that purpose.\n> This includes layout: use \\`HStack\\`, \\`VStack\\`, \\`SimpleGrid\\`, \\`Box\\`, \\`Container\\` instead of \\`<div>\\` with flex/grid classes.\n> This includes typography: use \\`Heading\\` and \\`Text\\` 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\\` / \\`HStack\\` | \\`<div className=\"flex ...\">\\` | Flexbox container with style props (\\`direction\\`, \\`align\\`, \\`justify\\`, \\`gap\\`, \\`wrap\\`) |\n| \\`SimpleGrid\\` | \\`<div className=\"grid ...\">\\` | CSS Grid container (\\`columns\\`, \\`spacing\\`) |\n| \\`VStack\\` | \\`<div className=\"flex flex-col gap-...\">\\` | Vertical stack (\\`spacing\\`) |\n| \\`HStack\\` | \\`<div className=\"flex flex-row gap-...\">\\` | Horizontal stack (\\`spacing\\`) |\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\n### Typography\n\n| Component | Use instead of | Description |\n|-----------|---------------|-------------|\n| \\`Text\\` | \\`<p>\\`, \\`<span>\\` | Text display with style props (\\`fontSize\\`, \\`fontWeight\\`, \\`color\\`, \\`textAlign\\`) |\n| \\`Heading\\` | \\`<h1>\\`–\\`<h6>\\`, \\`<p>\\` | Semantic heading elements (\\`as\\`: h1–h6, \\`size\\`: 2xl, xl, lg, md, sm, xs) |\n| \\`FormLabel\\` | \\`<label>\\` | Form label with accessibility support |\n\n### Cards & Containers\n\n- \\`Card\\`, \\`CardHeader\\`, \\`CardBody\\`, \\`CardFooter\\` — Use instead of styled \\`<div>\\` containers. Use \\`Heading\\` and \\`Text\\` inside for title/description.\n\n### Actions\n\n- \\`Button\\` — Use instead of \\`<button>\\` or \\`<a>\\` styled as buttons\n - Variants: \\`solid\\`, \\`outline\\`, \\`ghost\\`, \\`link\\`\n - Sizes: \\`xs\\`, \\`sm\\`, \\`md\\`, \\`lg\\`\n - Use \\`colorScheme\\` for color: \\`blue\\`, \\`red\\`, \\`green\\`, \\`gray\\`, etc.\n- \\`IconButton\\` — Use for icon-only buttons\n- \\`Menu\\`, \\`MenuButton\\`, \\`MenuList\\`, \\`MenuItem\\`, \\`MenuDivider\\`, \\`MenuGroup\\` — Use for action menus\n- \\`AlertDialog\\`, \\`AlertDialogOverlay\\`, \\`AlertDialogContent\\`, \\`AlertDialogHeader\\`, \\`AlertDialogBody\\`, \\`AlertDialogFooter\\` — Use for destructive action confirmations\n\n### Data Entry\n\n- \\`Input\\` — Use instead of \\`<input>\\`\n- \\`InputGroup\\`, \\`InputLeftElement\\`, \\`InputRightElement\\` — Use for input with icons/addons\n- \\`Textarea\\` — Use instead of \\`<textarea>\\`\n- \\`NumberInput\\`, \\`NumberInputField\\`, \\`NumberInputStepper\\`, \\`NumberIncrementStepper\\`, \\`NumberDecrementStepper\\` — Use for numeric inputs\n- \\`Select\\` — Use instead of \\`<select>\\`\n- \\`Checkbox\\`, \\`CheckboxGroup\\` — Use instead of \\`<input type=\"checkbox\">\\`\n- \\`Radio\\`, \\`RadioGroup\\` — Use instead of \\`<input type=\"radio\">\\`\n- \\`Switch\\` — Use for toggle switches\n- \\`Slider\\`, \\`SliderTrack\\`, \\`SliderFilledTrack\\`, \\`SliderThumb\\` — Use for range inputs\n- \\`PinInput\\`, \\`PinInputField\\` — Use for PIN/OTP code entry\n- \\`FormLabel\\` — Use instead of \\`<label>\\`\n\n### Data Display\n\n- \\`Table\\`, \\`Thead\\`, \\`Tbody\\`, \\`Tr\\`, \\`Th\\`, \\`Td\\`, \\`TableContainer\\` — Use instead of \\`<table>\\` elements\n- \\`Badge\\` — Use for status indicators, tags, counts (\\`colorScheme\\`: \\`green\\`, \\`red\\`, \\`blue\\`, \\`gray\\`, etc.)\n- \\`Avatar\\` — Use for user profile images (use \\`name\\` prop for fallback initials)\n- \\`Tooltip\\` — Use for hover hints (\\`label\\` prop for content)\n- \\`Stat\\`, \\`StatLabel\\`, \\`StatNumber\\`, \\`StatHelpText\\`, \\`StatArrow\\` — Use for metric/stat display\n- \\`Skeleton\\`, \\`SkeletonText\\`, \\`SkeletonCircle\\` — Use for loading placeholders\n- \\`Spinner\\` — Use for loading indicators\n- \\`Progress\\` — Use for progress bars\n- \\`Tag\\`, \\`TagLabel\\`, \\`TagCloseButton\\` — Use for removable tags\n\n### Tabs & Accordion\n\n- \\`Tabs\\`, \\`TabList\\`, \\`Tab\\`, \\`TabPanels\\`, \\`TabPanel\\` — Use for tabbed interfaces\n- \\`Accordion\\`, \\`AccordionItem\\`, \\`AccordionButton\\`, \\`AccordionPanel\\`, \\`AccordionIcon\\` — Use for collapsible sections\n\n### Overlays\n\n- \\`Modal\\`, \\`ModalOverlay\\`, \\`ModalContent\\`, \\`ModalHeader\\`, \\`ModalBody\\`, \\`ModalFooter\\`, \\`ModalCloseButton\\` — Use for modals\n- \\`AlertDialog\\` — Use for confirmation dialogs\n- \\`Drawer\\`, \\`DrawerOverlay\\`, \\`DrawerContent\\`, \\`DrawerHeader\\`, \\`DrawerBody\\`, \\`DrawerFooter\\`, \\`DrawerCloseButton\\` — Use for slide-out panels\n- \\`Popover\\`, \\`PopoverTrigger\\`, \\`PopoverContent\\`, \\`PopoverHeader\\`, \\`PopoverBody\\`, \\`PopoverArrow\\`, \\`PopoverCloseButton\\` — Use for floating content panels\n\n### Navigation\n\n- \\`Breadcrumb\\`, \\`BreadcrumbItem\\`, \\`BreadcrumbLink\\` — Use for breadcrumb trails\n\n### Feedback\n\n- \\`Alert\\`, \\`AlertIcon\\`, \\`AlertTitle\\`, \\`AlertDescription\\` — Use for inline messages/warnings (\\`status\\`: \\`error\\`, \\`warning\\`, \\`success\\`, \\`info\\`)\n- \\`useToast\\` — Use for transient notifications\n\n### Utilities & Hooks\n\n- \\`useColorMode()\\` — Color mode toggle. Returns \\`{ colorMode, toggleColorMode }\\`\n- \\`useDisclosure()\\` — Open/close state for modals, drawers, etc. Returns \\`{ isOpen, onOpen, onClose, onToggle }\\`\n- \\`useBreakpointValue()\\` — Responsive values based on breakpoint\n- \\`useToast()\\` — Programmatic toast notifications\n- \\`useMediaQuery()\\` — CSS media query matching\n\n---\n\n### Component-First Rules\n\n1. **Layout**: NEVER use \\`<div className=\"flex ...\">\\` or \\`<div className=\"grid ...\">\\`. Use \\`<Flex>\\`, \\`<SimpleGrid>\\`, \\`<VStack>\\`, \\`<HStack>\\`, \\`<Box>\\` from \\`@chakra-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 \\`<Heading as=\"h2\" size=\"lg\">\\` or \\`<Text>\\`.\n4. **Separators**: NEVER use \\`<hr>\\` or border hacks. Use \\`<Divider>\\`.\n5. **Buttons**: NEVER use \\`<button>\\` or \\`<a>\\` styled as a button. Use \\`<Button>\\`.\n6. **Inputs**: NEVER use \\`<input>\\`, \\`<textarea>\\`, \\`<select>\\` directly. Use the Chakra UI equivalents.\n7. **Cards**: NEVER use a styled \\`<div>\\` as a card. Use \\`Card\\` + sub-components.\n8. **Tables**: NEVER use raw \\`<table>\\` HTML. Use Chakra UI \\`Table\\` components.\n9. **Loading**: NEVER use custom \\`animate-pulse\\` divs. Use \\`Skeleton\\` or \\`Spinner\\`.\n10. **Modals**: NEVER build custom modals. Use \\`Modal\\`, \\`AlertDialog\\`, or \\`Drawer\\`.\n11. **Feedback**: NEVER use plain styled text for errors/warnings. Use \\`Alert\\` or \\`useToast\\`.\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\\`/\\`VStack\\`/\\`SimpleGrid\\` inside them for layout)\n- \\`<Link>\\` from react-router-dom — for page navigation (wrap with \\`<Button as={Link}>...\\` if it needs button styling)\n- Icon components from \\`lucide-react\\`\n- \\`<form>\\` element when used with React Hook Form (but use Chakra UI form components inside)\n\n### Design Tokens & Theming\n\n- \\`ChakraProvider\\` — Wrap app with \\`theme\\` prop to apply preset or custom theme\n- Extend theme with \\`extendTheme()\\` from \\`@chakra-ui/react\\`\n- All components use design tokens from the theme\n- Color mode: \\`useColorMode()\\` hook, or \\`ColorModeScript\\` in document head\n- Extend with \\`sx\\` prop or \\`style props\\` for custom styles\n\n### Example: HowItWorks Section (Correct Way)\n\n\\`\\`\\`tsx\nimport { Container, VStack, Flex, SimpleGrid, Text, Heading, Box } from '@chakra-ui/react'\nimport { howItWorksSteps } from '../data'\n\nexport function HowItWorks() {\n return (\n <Box as=\"section\" py={{ base: 16, sm: 20 }}>\n <Container maxW=\"container.xl\">\n <VStack spacing={3} align=\"center\" mb={12}>\n <Heading as=\"h2\" size=\"xl\">How It Works</Heading>\n <Text color=\"gray.500\">Book your stay in three simple steps</Text>\n </VStack>\n\n <SimpleGrid columns={{ base: 1, md: 3 }} spacing={8} maxW=\"4xl\" mx=\"auto\">\n {howItWorksSteps.map((item) => (\n <VStack key={item.step} align=\"center\" spacing={4}>\n <Box position=\"relative\">\n <Flex align=\"center\" justify=\"center\" h={16} w={16} rounded=\"full\" bg=\"blue.500\" color=\"white\" shadow=\"lg\">\n <item.icon size={28} />\n </Flex>\n <Flex align=\"center\" justify=\"center\" position=\"absolute\" top={-1} right={-1} h={6} w={6} rounded=\"full\" bg=\"white\" borderWidth={2} borderColor=\"blue.500\">\n <Text fontSize=\"xs\" fontWeight=\"bold\" color=\"blue.500\">{item.step}</Text>\n </Flex>\n </Box>\n <VStack spacing={2} align=\"center\">\n <Text fontSize=\"lg\" fontWeight=\"bold\">{item.title}</Text>\n <Text fontSize=\"sm\" color=\"gray.500\" textAlign=\"center\" maxW=\"xs\">\n {item.description}\n </Text>\n </VStack>\n </VStack>\n ))}\n </SimpleGrid>\n </Container>\n </Box>\n )\n}\n\\`\\`\\`\n\n### Example: Resource List Page (Correct Way)\n\n\\`\\`\\`tsx\nimport {\n VStack, Flex, Box,\n Card, CardHeader, CardBody,\n Button, Badge, Skeleton, Heading,\n Table, Thead, Tbody, Tr, Th, Td, TableContainer,\n Menu, MenuButton, MenuList, MenuItem, MenuDivider,\n AlertDialog, AlertDialogOverlay, AlertDialogContent,\n AlertDialogHeader, AlertDialogBody, AlertDialogFooter,\n IconButton, useDisclosure,\n} from '@chakra-ui/react'\nimport { MoreHorizontal, Plus, Trash2, Edit } from 'lucide-react'\nimport { Link } from 'react-router-dom'\nimport { useRef } from 'react'\n\nfunction ResourceListPage({ resources, isLoading, onDelete }) {\n const { isOpen, onOpen, onClose } = useDisclosure()\n const cancelRef = useRef()\n const [deleteId, setDeleteId] = useState(null)\n\n if (isLoading) {\n return (\n <Card>\n <CardBody p={6}>\n <VStack spacing={4}>\n {Array.from({ length: 5 }).map((_, i) => (\n <Skeleton key={i} h=\"48px\" w=\"full\" />\n ))}\n </VStack>\n </CardBody>\n </Card>\n )\n }\n\n return (\n <Card>\n <CardHeader>\n <Flex align=\"center\" justify=\"space-between\">\n <Heading size=\"md\">Resources</Heading>\n <Button as={Link} to=\"/resources/new\" leftIcon={<Plus size={16} />} colorScheme=\"blue\">\n Create\n </Button>\n </Flex>\n </CardHeader>\n <CardBody>\n <TableContainer>\n <Table>\n <Thead>\n <Tr>\n <Th>Title</Th>\n <Th>Status</Th>\n <Th w=\"48px\" />\n </Tr>\n </Thead>\n <Tbody>\n {resources.map((r) => (\n <Tr key={r.id}>\n <Td>{r.title}</Td>\n <Td><Badge variant=\"outline\">{r.status}</Badge></Td>\n <Td>\n <Menu>\n <MenuButton as={IconButton} icon={<MoreHorizontal size={16} />} variant=\"ghost\" size=\"sm\" />\n <MenuList>\n <MenuItem as={Link} to={\\`/resources/\\${r.id}/edit\\`} icon={<Edit size={16} />}>\n Edit\n </MenuItem>\n <MenuDivider />\n <MenuItem icon={<Trash2 size={16} />} color=\"red.500\" onClick={() => { setDeleteId(r.id); onOpen() }}>\n Delete\n </MenuItem>\n </MenuList>\n </Menu>\n </Td>\n </Tr>\n ))}\n </Tbody>\n </Table>\n </TableContainer>\n </CardBody>\n\n <AlertDialog isOpen={isOpen} leastDestructiveRef={cancelRef} onClose={onClose}>\n <AlertDialogOverlay>\n <AlertDialogContent>\n <AlertDialogHeader>Delete Resource</AlertDialogHeader>\n <AlertDialogBody>Are you sure? This cannot be undone.</AlertDialogBody>\n <AlertDialogFooter>\n <Button ref={cancelRef} onClick={onClose}>Cancel</Button>\n <Button colorScheme=\"red\" onClick={() => { onDelete(deleteId); onClose() }} ml={3}>\n Delete\n </Button>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialogOverlay>\n </AlertDialog>\n </Card>\n )\n}\n\\`\\`\\`\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const chakraUiFormsSkill: Skill = {\n id: 'chakra-ui-forms',\n name: 'Chakra UI Forms',\n description: 'Form components using Chakra UI + React Hook Form + Zod for validation and submission.',\n\n render(_ctx: SkillContext): string {\n return `## Chakra UI Forms — Form Components (React Hook Form + Zod)\n\n> **RULE: ALWAYS use Chakra UI form components for forms.** Do NOT build forms with raw \\`<form>\\`, \\`<input>\\`, \\`<label>\\`, or manual error display.\n\n\\`\\`\\`tsx\nimport {\n FormControl, FormLabel, FormErrorMessage, FormHelperText,\n Input, Textarea, Select, Checkbox, Switch, NumberInput,\n NumberInputField, NumberInputStepper, NumberIncrementStepper, NumberDecrementStepper,\n Radio, RadioGroup, Button,\n} from '@chakra-ui/react'\n\\`\\`\\`\n\n### Components\n\n- \\`FormControl\\` — Wraps each field. Props: \\`isInvalid\\`, \\`isRequired\\`, \\`isDisabled\\`\n- \\`FormLabel\\` — Label for a form field\n- \\`FormErrorMessage\\` — Displays field-level validation error (shown when \\`FormControl\\` has \\`isInvalid\\`)\n- \\`FormHelperText\\` — Displays helper text below a field\n- \\`Input\\` — Text input. Props: \\`type\\`, \\`placeholder\\`, \\`size\\`\n- \\`Textarea\\` — Textarea. Props: \\`rows\\`, \\`placeholder\\`\n- \\`Select\\` — Select dropdown. Props: \\`placeholder\\`\n- \\`Checkbox\\` — Checkbox input\n- \\`Switch\\` — Toggle switch\n- \\`RadioGroup\\` + \\`Radio\\` — Radio group\n- \\`NumberInput\\` — Numeric input with stepper\n\n### Rules\n- NEVER use raw \\`<form>\\` with manual \\`<label>\\` and error \\`<p>\\` tags. Always use \\`FormControl\\` + \\`FormLabel\\` + \\`FormErrorMessage\\`.\n- NEVER use \\`<input>\\`, \\`<textarea>\\`, \\`<select>\\` inside forms. Use Chakra UI \\`Input\\`, \\`Textarea\\`, \\`Select\\`.\n\n### Form Pattern — ALWAYS follow this:\n\\`\\`\\`tsx\nimport {\n FormControl, FormLabel, FormErrorMessage,\n Input, Textarea, Select, Button, VStack,\n} from '@chakra-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 { register, handleSubmit, formState: { errors } } = useForm<FormData>({\n resolver: zodResolver(schema),\n defaultValues: { title: '', description: '', status: 'draft', ...defaultValues },\n })\n\n return (\n <form onSubmit={handleSubmit(onSubmit)}>\n <VStack spacing={4} align=\"stretch\">\n <FormControl isInvalid={!!errors.title} isRequired>\n <FormLabel>Title</FormLabel>\n <Input placeholder=\"Enter title\" {...register('title')} />\n <FormErrorMessage>{errors.title?.message}</FormErrorMessage>\n </FormControl>\n\n <FormControl isInvalid={!!errors.description}>\n <FormLabel>Description</FormLabel>\n <Textarea rows={4} placeholder=\"Enter description\" {...register('description')} />\n <FormErrorMessage>{errors.description?.message}</FormErrorMessage>\n </FormControl>\n\n <FormControl isInvalid={!!errors.status}>\n <FormLabel>Status</FormLabel>\n <Select {...register('status')}>\n <option value=\"draft\">Draft</option>\n <option value=\"published\">Published</option>\n </Select>\n <FormErrorMessage>{errors.status?.message}</FormErrorMessage>\n </FormControl>\n\n <Button type=\"submit\" colorScheme=\"blue\" isLoading={isSubmitting}>\n Save\n </Button>\n </VStack>\n </form>\n )\n}\n\\`\\`\\`\n\n### Example: Detail Page with Edit Dialog\n\\`\\`\\`tsx\nimport {\n Card, CardHeader, CardBody, CardFooter,\n Button, Badge, Divider, Heading, Text,\n Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalFooter, ModalCloseButton,\n Alert, AlertIcon, AlertTitle, AlertDescription,\n FormControl, FormLabel, FormErrorMessage,\n Input, Textarea, Flex, HStack, useDisclosure,\n} from '@chakra-ui/react'\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 { isOpen, onOpen, onClose } = useDisclosure()\n const { register, handleSubmit, formState: { errors } } = useForm({\n resolver: zodResolver(editSchema),\n defaultValues: { title: resource.title, description: resource.description },\n })\n\n return (\n <Card>\n <CardHeader>\n <Flex align=\"center\" justify=\"space-between\">\n <Box>\n <Heading size=\"md\">{resource.title}</Heading>\n <Text fontSize=\"sm\" color=\"gray.500\">Created {new Date(resource.created_at).toLocaleDateString()}</Text>\n </Box>\n <HStack spacing={2}>\n <Button variant=\"outline\" as={Link} to=\"/resources\" leftIcon={<ArrowLeft size={16} />}>\n Back\n </Button>\n <Button leftIcon={<Edit size={16} />} colorScheme=\"blue\" onClick={onOpen}>\n Edit\n </Button>\n </HStack>\n </Flex>\n </CardHeader>\n <Divider />\n <CardBody>\n {error && (\n <Alert status=\"error\" mb={4}>\n <AlertIcon />\n <AlertTitle>Error</AlertTitle>\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n )}\n <Text>{resource.description || 'No description provided.'}</Text>\n </CardBody>\n <CardFooter>\n <Badge>{resource.status}</Badge>\n </CardFooter>\n\n <Modal isOpen={isOpen} onClose={onClose}>\n <ModalOverlay />\n <ModalContent>\n <ModalHeader>Edit Resource</ModalHeader>\n <ModalCloseButton />\n <form onSubmit={handleSubmit(onUpdate)}>\n <ModalBody>\n <VStack spacing={4}>\n <FormControl isInvalid={!!errors.title}>\n <FormLabel>Title</FormLabel>\n <Input {...register('title')} />\n <FormErrorMessage>{errors.title?.message}</FormErrorMessage>\n </FormControl>\n <FormControl isInvalid={!!errors.description}>\n <FormLabel>Description</FormLabel>\n <Textarea rows={4} {...register('description')} />\n <FormErrorMessage>{errors.description?.message}</FormErrorMessage>\n </FormControl>\n </VStack>\n </ModalBody>\n <ModalFooter>\n <Button type=\"submit\" colorScheme=\"blue\">Save Changes</Button>\n </ModalFooter>\n </form>\n </ModalContent>\n </Modal>\n </Card>\n )\n}\n\\`\\`\\`\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const chakraUiAuthSkill: Skill = {\n id: 'chakra-ui-auth',\n name: 'Custom Auth System',\n description: 'Custom authentication context, hooks, and types for login, registration, and password reset.',\n\n render(_ctx: SkillContext): string {\n return `## Custom Auth System — Authentication Context & Hooks\n\n> **RULE: Use the local AuthProvider and useAuth hook for all auth functionality.**\n> Auth components (LoginForm, RegisterForm, etc.) are custom implementations in \\`frontend/src/features/auth/\\`.\n\n\\`\\`\\`tsx\nimport { AuthProvider } from '@/features/auth/context'\nimport { useAuth } from '@/features/auth/hooks/use-auth'\nimport type { User, AuthState } from '@/features/auth/types'\n\\`\\`\\`\n\n### Context & Provider\n\n- \\`AuthProvider\\` — Context provider wrapping the app. Manages auth state, token storage, and session lifecycle.\n - Place at the app root, inside \\`ChakraProvider\\`.\n - Props: \\`config?: { adapter?: AuthAdapter }\\`\n\n### Types\n\n\\`\\`\\`tsx\ninterface User {\n id: string\n email: string\n displayName?: string\n avatar?: string\n}\n\ninterface AuthState {\n user: User | null\n loading: boolean\n error: string | null\n isAuthenticated: boolean\n}\n\\`\\`\\`\n\n### Hooks\n\n- \\`useAuth()\\` — Hook for auth state and actions\n - Returns: \\`user\\`, \\`loading\\`, \\`error\\`, \\`isAuthenticated\\`, \\`signInWithEmail(email, password)\\`, \\`signUpWithEmail(email, password, displayName?)\\`, \\`signOut()\\`, \\`sendPasswordResetEmail(email)\\`, \\`confirmPasswordReset(code, newPassword)\\`\n\n### Auth Pages\n\nAuth pages are custom implementations in \\`frontend/src/features/auth/pages/\\`:\n\n- \\`LoginPage\\` — Login form with email/password, validation, and navigation links\n- \\`RegisterPage\\` — Registration form with email, password, and display name\n- \\`ForgotPasswordPage\\` — Password reset email request\n- \\`ResetPasswordPage\\` — Set new password form\n\nEach auth page uses Chakra UI form components (\\`FormControl\\`, \\`FormLabel\\`, \\`Input\\`, \\`Button\\`, etc.) with React Hook Form + Zod validation.\n\n### Adapter\n\n- \\`AuthAdapter\\` — Interface for custom auth backends (Django JWT adapter in \\`frontend/src/features/auth/adapter.ts\\`)\n\n### Rules\n- NEVER manage auth state manually. Use \\`useAuth()\\` hook.\n- Auth pages live in \\`frontend/src/features/auth/pages/\\` and use Chakra UI components.\n- Use the \\`Path\\` enum for auth route paths (\\`Path.Login\\`, \\`Path.Register\\`, etc.).\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const blacksmithHooksSkill: Skill = {\n id: 'blacksmith-hooks',\n name: 'Custom Hooks & Chakra UI Hooks',\n description: 'Custom React hooks (local implementations) and Chakra UI built-in hooks for state, UI, layout, and responsiveness.',\n\n render(_ctx: SkillContext): string {\n return `## Custom Hooks & Chakra UI Hooks\n\nA combination of local custom hooks and Chakra UI built-in hooks for common UI patterns.\n\n> **RULE: Use Chakra UI hooks when available, and local custom hooks for additional utilities.**\n> Before creating a new hook, check if one already exists below.\n\n### Chakra UI Built-in Hooks\n\n\\`\\`\\`tsx\nimport { useColorMode, useDisclosure, useBreakpointValue, useMediaQuery, useToast } from '@chakra-ui/react'\n\\`\\`\\`\n\n| Hook | Description |\n|------|-------------|\n| \\`useColorMode\\` | Color mode toggle. Returns \\`{ colorMode, toggleColorMode, setColorMode }\\` |\n| \\`useColorModeValue\\` | Returns a value based on current color mode: \\`useColorModeValue(lightValue, darkValue)\\` |\n| \\`useDisclosure\\` | Open/close/toggle state for modals, drawers, popovers. Returns \\`{ isOpen, onOpen, onClose, onToggle }\\` |\n| \\`useBreakpointValue\\` | Responsive values: \\`useBreakpointValue({ base: 1, md: 2, lg: 3 })\\` |\n| \\`useMediaQuery\\` | CSS media query matching: \\`useMediaQuery('(min-width: 768px)')\\` |\n| \\`useToast\\` | Programmatic toast notifications |\n| \\`useClipboard\\` | Copy text to clipboard with status feedback |\n| \\`useBoolean\\` | Boolean state with \\`on\\`, \\`off\\`, \\`toggle\\` actions |\n| \\`useOutsideClick\\` | Detect clicks outside a ref element |\n| \\`useControllable\\` | Controlled/uncontrolled component pattern helper |\n| \\`useMergeRefs\\` | Merge multiple refs into one |\n| \\`useTheme\\` | Access the current Chakra UI theme object |\n\n### Custom Local Hooks\n\nThese are implemented locally in the project (e.g., in \\`frontend/src/shared/hooks/\\`):\n\n\\`\\`\\`tsx\nimport { useDebounce } from '@/shared/hooks/use-debounce'\nimport { useLocalStorage } from '@/shared/hooks/use-local-storage'\n\\`\\`\\`\n\n| Hook | Description |\n|------|-------------|\n| \\`useDebounce\\` | Debounce a value with configurable delay |\n| \\`useDebouncedCallback\\` | Debounce a callback function |\n| \\`useLocalStorage\\` | Persist state to localStorage with JSON serialization |\n| \\`useSessionStorage\\` | Persist state to sessionStorage with JSON serialization |\n| \\`usePrevious\\` | Track the previous value of a variable |\n| \\`useInterval\\` | setInterval wrapper with pause support |\n| \\`useTimeout\\` | setTimeout wrapper with manual clear |\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| \\`useScrollPosition\\` | Track window scroll position |\n| \\`useScrollLock\\` | Lock/unlock body scroll |\n| \\`useOnline\\` | Track network connectivity |\n| \\`useWindowSize\\` | Track window dimensions |\n| \\`usePageVisibility\\` | Detect page visibility state |\n| \\`useIsMounted\\` | Check if component is currently mounted |\n| \\`useIsFirstRender\\` | Check if this is the first render |\n| \\`useUpdateEffect\\` | useEffect that skips the initial render |\n| \\`useIntersectionObserver\\` | Observe element intersection with viewport |\n| \\`useInfiniteScroll\\` | Infinite scroll with threshold detection |\n\n### Common Patterns\n\n**Modal with Chakra disclosure:**\n\\`\\`\\`tsx\nimport { useDisclosure } from '@chakra-ui/react'\nimport { Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalCloseButton, Button } from '@chakra-ui/react'\n\nfunction MyComponent() {\n const { isOpen, onOpen, onClose } = useDisclosure()\n\n return (\n <>\n <Button onClick={onOpen}>Open</Button>\n <Modal isOpen={isOpen} onClose={onClose}>\n <ModalOverlay />\n <ModalContent>\n <ModalHeader>Modal Title</ModalHeader>\n <ModalCloseButton />\n <ModalBody>Modal content here</ModalBody>\n </ModalContent>\n </Modal>\n </>\n )\n}\n\\`\\`\\`\n\n**Debounced search (custom hook):**\n\\`\\`\\`tsx\nimport { useDebounce } from '@/shared/hooks/use-debounce'\nimport { Input } from '@chakra-ui/react'\n\nfunction SearchPage({ items }) {\n const [query, setQuery] = useState('')\n const debouncedQuery = useDebounce(query, 300)\n // Use debouncedQuery for API calls or filtering\n\n return (\n <Input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search...\"\n />\n )\n}\n\\`\\`\\`\n\n**Responsive layout with Chakra hook:**\n\\`\\`\\`tsx\nimport { useBreakpointValue } from '@chakra-ui/react'\n\nfunction Layout({ children }) {\n const columns = useBreakpointValue({ base: 1, md: 2, lg: 3 })\n const isMobile = useBreakpointValue({ base: true, md: false })\n\n return isMobile ? <MobileLayout>{children}</MobileLayout> : <DesktopLayout>{children}</DesktopLayout>\n}\n\\`\\`\\`\n\n**Color mode toggle:**\n\\`\\`\\`tsx\nimport { useColorMode, useColorModeValue, IconButton } from '@chakra-ui/react'\nimport { Sun, Moon } from 'lucide-react'\n\nfunction ColorModeToggle() {\n const { colorMode, toggleColorMode } = useColorMode()\n const icon = colorMode === 'light' ? <Moon size={16} /> : <Sun size={16} />\n\n return <IconButton aria-label=\"Toggle color mode\" icon={icon} onClick={toggleColorMode} variant=\"ghost\" />\n}\n\\`\\`\\`\n\n**Persisted state with local storage:**\n\\`\\`\\`tsx\nimport { useLocalStorage } from '@/shared/hooks/use-local-storage'\n\nfunction Editor() {\n const [saved, setSaved] = useLocalStorage('draft', '')\n // saved persists across page refreshes\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-chakra-ui-skill\\` | Exclude Chakra 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 Chakra UI 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> Chakra UI follows a clean design language similar to 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 Chakra UI's spacing scale consistently. Chakra uses a numeric spacing scale (1 = 4px, 2 = 8px, etc.).\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 \\`spacing\\` (via \\`VStack\\`, \\`HStack\\`, \\`SimpleGrid\\`) for spacing between siblings — not margin on individual items\n- Use \\`VStack spacing={...}\\` for vertical rhythm within a section\n- Page content padding: use \\`Container\\` which handles responsive horizontal padding\n- Card body padding: \\`p={6}\\` standard, \\`p={4}\\` for compact cards\n- Never mix spacing approaches in the same context — pick spacing OR margin, not both\n\n### Typography\n\nUse \\`Heading\\` and \\`Text\\` components from \\`@chakra-ui/react\\`. Do NOT style raw HTML headings.\n\n**Hierarchy:**\n| Level | Component | Use for |\n|-------|-----------|---------|\n| Page title | \\`<Heading as=\"h1\" size=\"2xl\">\\` | One per page. The main heading. |\n| Section title | \\`<Heading as=\"h2\" size=\"xl\">\\` | Major sections within a page |\n| Sub-section | \\`<Heading as=\"h3\" size=\"lg\">\\` | Groups within a section |\n| Card title | \\`<Heading as=\"h4\" size=\"md\">\\` | Card headings |\n| Body | \\`<Text>\\` | Paragraphs, descriptions |\n| Caption/label | \\`<Text fontSize=\"sm\" color=\"gray.500\">\\` | Secondary info, metadata, timestamps |\n| Overline | \\`<Text fontSize=\"xs\" fontWeight=\"medium\" textTransform=\"uppercase\" letterSpacing=\"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: \\`fontSize=\"sm\"\\` (14px) for dense UIs (tables, sidebars), \\`fontSize=\"md\"\\` (16px) for reading content\n- Max reading width: \\`maxW=\"prose\"\\` (~65ch) for long-form text. Never let paragraphs stretch full-width\n- Use \\`color=\"gray.500\"\\` or theme-aware \\`useColorModeValue('gray.600', 'gray.400')\\` for secondary text\n- Font weight: \\`fontWeight=\"medium\"\\` (500) for labels, \\`fontWeight=\"semibold\"\\` (600) for headings, \\`fontWeight=\"bold\"\\` (700) sparingly\n\n### Color\n\nUse Chakra UI's theme-aware color tokens, never hardcoded colors.\n\n**Semantic palette (via colorScheme and theme tokens):**\n| Token | Usage |\n|-------|-------|\n| \\`blue\\` (colorScheme) | Primary actions (buttons, links, active states) |\n| \\`gray\\` (colorScheme) | Secondary actions, subtle backgrounds |\n| \\`red\\` (colorScheme) | Delete, error, danger states |\n| \\`green\\` (colorScheme) | Success states |\n| \\`yellow\\` / \\`orange\\` | Warning states |\n\n**Rules:**\n- Use \\`useColorModeValue()\\` for any colors that need to adapt between light and dark mode\n- Status colors: use \\`Badge\\` with \\`colorScheme\\` (\\`green\\`, \\`red\\`, \\`blue\\`, \\`gray\\`) — 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 maxW=\"container.xl\">\n <VStack spacing={8} align=\"stretch\">\n {/* Page header */}\n <Flex align=\"center\" justify=\"space-between\">\n <VStack spacing={1} align=\"start\">\n <Heading as=\"h1\" size=\"2xl\">Page Title</Heading>\n <Text color=\"gray.500\">Brief description of this page</Text>\n </VStack>\n <Button colorScheme=\"blue\">Primary Action</Button>\n </Flex>\n\n {/* Page content sections */}\n <VStack spacing={6} align=\"stretch\">\n {/* ... */}\n </VStack>\n </VStack>\n </Container>\n</Box>\n\\`\\`\\`\n\n**Card-based content:**\n\\`\\`\\`tsx\n<SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} spacing={6}>\n {items.map((item) => (\n <Card key={item.id}>\n <CardHeader>\n <Heading size=\"md\">{item.title}</Heading>\n <Text fontSize=\"sm\" color=\"gray.500\">{item.description}</Text>\n </CardHeader>\n <CardBody>\n {/* Content */}\n </CardBody>\n </Card>\n ))}\n</SimpleGrid>\n\\`\\`\\`\n\n**Sidebar + main content:**\n\\`\\`\\`tsx\n<Flex minH=\"100vh\">\n <Box as=\"aside\" w=\"250px\">{/* Nav items */}</Box>\n <Box as=\"main\" flex={1}>\n <Container maxW=\"container.xl\">{/* Page content */}</Container>\n </Box>\n</Flex>\n\\`\\`\\`\n\n**Section with centered content (landing pages):**\n\\`\\`\\`tsx\n<Box as=\"section\" py={{ base: 16, sm: 20 }}>\n <Container maxW=\"container.xl\">\n <VStack spacing={4} align=\"center\" textAlign=\"center\">\n <Heading as=\"h2\" size=\"xl\">Section Title</Heading>\n <Text color=\"gray.500\" maxW=\"2xl\">\n A concise description that explains the value proposition.\n </Text>\n </VStack>\n <SimpleGrid columns={{ base: 1, md: 3 }} spacing={8} mt={12}>\n {/* Feature cards or content */}\n </SimpleGrid>\n </Container>\n</Box>\n\\`\\`\\`\n\n### Component Patterns\n\n**Empty states:**\n\\`\\`\\`tsx\n// GOOD — uses a well-structured empty state with Chakra components\n<VStack spacing={4} align=\"center\" py={12} textAlign=\"center\">\n <Icon as={Inbox} boxSize={12} color=\"gray.400\" />\n <Heading size=\"md\">No messages yet</Heading>\n <Text color=\"gray.500\">Messages from your team will appear here.</Text>\n <Button colorScheme=\"blue\">Send a message</Button>\n</VStack>\n\n// BAD — hand-rolled empty state with raw HTML\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 Chakra Stat component\n<SimpleGrid columns={{ base: 1, sm: 2, lg: 4 }} spacing={4}>\n <Stat>\n <StatLabel>Total Users</StatLabel>\n <StatNumber>2,847</StatNumber>\n <StatHelpText><StatArrow type=\"increase\" />12%</StatHelpText>\n </Stat>\n <Stat>\n <StatLabel>Revenue</StatLabel>\n <StatNumber>$48,290</StatNumber>\n <StatHelpText><StatArrow type=\"increase\" />8%</StatHelpText>\n </Stat>\n</SimpleGrid>\n\\`\\`\\`\n\n**Loading states:**\n\\`\\`\\`tsx\n// GOOD — Skeleton matches the layout structure\n<VStack spacing={4} align=\"stretch\">\n <Skeleton h=\"32px\" w=\"200px\" />\n <SkeletonText noOfLines={2} />\n <SimpleGrid columns={3} spacing={4}>\n {Array.from({ length: 3 }).map((_, i) => (\n <Skeleton key={i} h=\"128px\" />\n ))}\n </SimpleGrid>\n</VStack>\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\nChakra UI supports color mode via \\`useColorMode()\\` and \\`useColorModeValue()\\`. All built-in components automatically adapt.\n\n**Rules:**\n- Use \\`useColorModeValue()\\` for any custom colors that need to differ between modes\n- NEVER hardcode colors that only work in one mode. Use theme tokens or \\`useColorModeValue()\\`.\n- NEVER use \\`bg=\"white\"\\` or \\`bg=\"black\"\\`. Use \\`bg={useColorModeValue('white', 'gray.800')}\\` or Chakra semantic tokens.\n- All Chakra UI components automatically adapt to color mode — leverage this.\n\n### Interactions & Feedback\n\n- **Hover states**: Subtle background change — Chakra components handle this automatically\n- **Focus**: Chakra components include accessible focus rings by default\n- **Loading feedback**: Show \\`Spinner\\` on buttons via \\`isLoading\\` prop. 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**: Chakra's responsive props use mobile-first breakpoints\n- **Breakpoints**: \\`sm\\` (480px), \\`md\\` (768px), \\`lg\\` (992px), \\`xl\\` (1280px), \\`2xl\\` (1536px)\n- **Responsive props**: \\`columns={{ base: 1, md: 2, lg: 3 }}\\` — single column on mobile, expand on larger screens\n- **Hide/show**: Use Chakra's \\`Show\\` and \\`Hide\\` components or \\`display={{ base: 'none', md: 'block' }}\\`\n- **Touch targets**: Minimum 44x44px for interactive elements on mobile. Use \\`Button size=\"lg\"\\` and adequate padding\n- **Stack direction**: Use \\`Stack direction={{ base: 'column', md: 'row' }}\\` for responsive stacking\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| Raw \\`<div>\\` with flex/grid classes | Use \\`Flex\\`, \\`VStack\\`, \\`HStack\\`, \\`SimpleGrid\\` |\n| Raw \\`<h1>\\`-\\`<h6>\\` tags | Use \\`Heading\\` with \\`as\\` and \\`size\\` props |\n| Raw \\`<p>\\` tags | Use \\`Text\\` |\n| Heavy box shadows | Use Chakra's built-in shadow prop: \\`shadow=\"sm\"\\`, \\`shadow=\"md\"\\` |\n| Gradient backgrounds on surfaces | Use solid backgrounds |\n| Custom scrollbar CSS hacks | Use Chakra's styling system |\n| Animated entrances (fade-in, slide-up) | Content should appear instantly. Only animate user-triggered changes |\n| Using \\`<br />\\` for spacing | Use \\`VStack spacing={...}\\` or Chakra spacing props |\n| Inline styles (\\`style={{ ... }}\\`) | Use Chakra style props (\\`p\\`, \\`m\\`, \\`bg\\`, \\`color\\`, etc.) |\n| Hardcoded color values | Use theme tokens and \\`useColorModeValue()\\` |\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 (ChakraProvider, 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 auth hook (for auth page tests):**\n\\`\\`\\`tsx\nvi.mock('@/features/auth/hooks/use-auth', () => ({\n useAuth: () => ({\n user: null,\n loading: false,\n error: null,\n isAuthenticated: false,\n signInWithEmail: vi.fn(),\n signOut: vi.fn(),\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 \\`@chakra-ui/react\\` layout components (\\`VStack\\`, \\`HStack\\`, \\`Flex\\`, \\`SimpleGrid\\`) — 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 \\`@chakra-ui/react\\` for ALL UI** — \\`VStack\\`, \\`HStack\\`, \\`Flex\\`, \\`SimpleGrid\\` for layout; \\`Heading\\`, \\`Text\\` for text; \\`Card\\`, \\`Button\\`, \\`Badge\\`, etc. for all elements. Never use raw HTML (\\`<div>\\`, \\`<h1>\\`, \\`<p>\\`, \\`<button>\\`) when a Chakra 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 \\`@chakra-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 { chakraUiReactSkill } from '../skills/chakra-ui-react.js'\nimport { chakraUiFormsSkill } from '../skills/chakra-ui-forms.js'\nimport { chakraUiAuthSkill } from '../skills/chakra-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 chakraUiReactSkill,\n chakraUiFormsSkill,\n chakraUiAuthSkill,\n blacksmithHooksSkill,\n blacksmithCliSkill,\n frontendTestingSkill,\n cleanCodeSkill,\n aiGuidelinesSkill,\n]\n\ninterface SetupOptions {\n chakraUiSkill?: 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 includeChakraUiSkill: options.chakraUiSkill !== 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 fs from 'node:fs'\nimport path from 'node:path'\nimport { findProjectRoot } from '../utils/paths.js'\nimport { log, promptSelect, promptText, promptYesNo } from '../utils/logger.js'\n\ninterface McpPrompt {\n label: string\n defaultValue?: string\n target: 'args' | 'env'\n envVar?: string\n}\n\ninterface McpServerPreset {\n id: string\n name: string\n description: string\n command: string\n args: string[]\n env?: Record<string, string>\n prompts?: McpPrompt[]\n}\n\ninterface McpServerConfig {\n command: string\n args: string[]\n env?: Record<string, string>\n}\n\nconst DONE_OPTION = 'Done — save and exit'\n\nfunction getPresets(projectRoot: string): McpServerPreset[] {\n return [\n {\n id: 'filesystem',\n name: 'Filesystem',\n description: 'Read and write project files',\n command: 'npx',\n args: ['-y', '@modelcontextprotocol/server-filesystem'],\n prompts: [\n {\n label: 'Allowed directory path',\n defaultValue: projectRoot,\n target: 'args',\n },\n ],\n },\n {\n id: 'postgres',\n name: 'PostgreSQL',\n description: 'Query PostgreSQL databases',\n command: 'npx',\n args: ['-y', '@modelcontextprotocol/server-postgres'],\n prompts: [\n {\n label: 'PostgreSQL connection string (e.g. postgresql://user:pass@localhost:5432/dbname)',\n target: 'args',\n },\n ],\n },\n {\n id: 'fetch',\n name: 'Fetch',\n description: 'Make HTTP requests and fetch web content',\n command: 'npx',\n args: ['-y', '@modelcontextprotocol/server-fetch'],\n },\n {\n id: 'github',\n name: 'GitHub',\n description: 'Interact with GitHub repos, issues, and PRs',\n command: 'npx',\n args: ['-y', '@modelcontextprotocol/server-github'],\n prompts: [\n {\n label: 'GitHub personal access token',\n target: 'env',\n envVar: 'GITHUB_PERSONAL_ACCESS_TOKEN',\n },\n ],\n },\n {\n id: 'chakra-ui-docs',\n name: 'Chakra UI Docs',\n description: 'Chakra UI component documentation for AI assistance',\n command: 'npx',\n args: [\n '-y',\n '@anthropic-ai/mcp-docs-server',\n '--url',\n 'https://www.chakra-ui.com/docs',\n '--name',\n 'chakra-ui-docs',\n ],\n },\n {\n id: 'sentry',\n name: 'Sentry',\n description: 'Query errors, view issues, and manage releases in Sentry',\n command: 'npx',\n args: ['-y', '@sentry/mcp-server'],\n prompts: [\n {\n label: 'Sentry auth token',\n target: 'env',\n envVar: 'SENTRY_AUTH_TOKEN',\n },\n {\n label: 'Sentry organization slug',\n target: 'env',\n envVar: 'SENTRY_ORG',\n },\n ],\n },\n {\n id: 'puppeteer',\n name: 'Puppeteer',\n description: 'Browser automation for testing, screenshots, and scraping',\n command: 'npx',\n args: ['-y', '@modelcontextprotocol/server-puppeteer'],\n },\n {\n id: 'memory',\n name: 'Memory',\n description: 'Persistent knowledge graph for cross-session context',\n command: 'npx',\n args: ['-y', '@modelcontextprotocol/server-memory'],\n },\n {\n id: 'slack',\n name: 'Slack',\n description: 'Read/send messages, search channels, and manage threads',\n command: 'npx',\n args: ['-y', '@anthropic-ai/mcp-server-slack'],\n prompts: [\n {\n label: 'Slack bot token (xoxb-...)',\n target: 'env',\n envVar: 'SLACK_BOT_TOKEN',\n },\n {\n label: 'Slack team ID',\n target: 'env',\n envVar: 'SLACK_TEAM_ID',\n },\n ],\n },\n {\n id: 'redis',\n name: 'Redis',\n description: 'Query and manage Redis cache',\n command: 'npx',\n args: ['-y', '@modelcontextprotocol/server-redis'],\n prompts: [\n {\n label: 'Redis URL (e.g. redis://localhost:6379)',\n defaultValue: 'redis://localhost:6379',\n target: 'env',\n envVar: 'REDIS_URL',\n },\n ],\n },\n ]\n}\n\nfunction readSettings(settingsPath: string): Record<string, any> {\n if (!fs.existsSync(settingsPath)) {\n return {}\n }\n\n try {\n return JSON.parse(fs.readFileSync(settingsPath, 'utf-8'))\n } catch {\n return {}\n }\n}\n\nfunction buildServerConfig(preset: McpServerPreset, promptValues: string[]): McpServerConfig {\n const args = [...preset.args]\n const env: Record<string, string> = { ...preset.env }\n\n let valueIndex = 0\n for (const prompt of preset.prompts || []) {\n const value = promptValues[valueIndex++]\n if (prompt.target === 'args') {\n args.push(value)\n } else if (prompt.target === 'env' && prompt.envVar) {\n env[prompt.envVar] = value\n }\n }\n\n const config: McpServerConfig = { command: preset.command, args }\n if (Object.keys(env).length > 0) {\n config.env = env\n }\n return config\n}\n\nexport async function setupMcp() {\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 settingsPath = path.join(root, '.claude', 'settings.local.json')\n const settings = readSettings(settingsPath)\n const existingServers: Record<string, any> = settings.mcpServers || {}\n const newServers: Record<string, McpServerConfig> = {}\n const presets = getPresets(root)\n\n log.blank()\n log.info('Configure MCP servers for Claude Code.')\n log.info('Select servers to add, then choose \"Done\" to save.')\n log.blank()\n\n while (true) {\n const options = presets.map((p) => {\n const configured = existingServers[p.id] || newServers[p.id]\n const suffix = configured ? ' (configured)' : ''\n return `${p.name} — ${p.description}${suffix}`\n })\n options.push(DONE_OPTION)\n\n const choice = await promptSelect('Select an MCP server to configure', options)\n\n if (choice === DONE_OPTION) {\n break\n }\n\n const selectedIndex = options.indexOf(choice)\n const preset = presets[selectedIndex]\n\n if (!preset) break\n\n // Check if already configured\n if (existingServers[preset.id] || newServers[preset.id]) {\n const overwrite = await promptYesNo(\n `${preset.name} is already configured. Overwrite?`,\n false,\n )\n if (!overwrite) continue\n }\n\n // Collect prompt values\n const values: string[] = []\n let skipped = false\n\n for (const prompt of preset.prompts || []) {\n const value = await promptText(prompt.label, prompt.defaultValue)\n if (!value && !prompt.defaultValue) {\n log.warn(`Skipping ${preset.name} — required value not provided.`)\n skipped = true\n break\n }\n values.push(value)\n }\n\n if (skipped) continue\n\n newServers[preset.id] = buildServerConfig(preset, values)\n log.success(`${preset.name} configured.`)\n log.blank()\n }\n\n if (Object.keys(newServers).length === 0) {\n log.info('No new servers configured.')\n return\n }\n\n // Merge and write\n settings.mcpServers = { ...existingServers, ...newServers }\n\n try {\n fs.mkdirSync(path.join(root, '.claude'), { recursive: true })\n fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n', 'utf-8')\n } catch (error: any) {\n log.error(`Failed to write settings: ${error.message}`)\n process.exit(1)\n }\n\n log.blank()\n log.success('MCP servers configured:')\n for (const id of Object.keys(newServers)) {\n const preset = presets.find((p) => p.id === id)\n log.step(` ${preset?.name || id}`)\n }\n log.blank()\n log.info(`Settings written to ${path.relative(root, settingsPath)}`)\n log.blank()\n}\n","import net from 'node:net'\nimport { findProjectRoot, loadConfig } from '../utils/paths.js'\nimport { log } from '../utils/logger.js'\n\nconst DEFAULT_PORT = 3939\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\ninterface StudioOptions {\n port?: string\n}\n\nexport async function studio(options: StudioOptions) {\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 requestedPort = options.port ? parseInt(options.port, 10) : DEFAULT_PORT\n\n let port: number\n try {\n port = await findAvailablePort(requestedPort)\n } catch (err) {\n log.error((err as Error).message)\n process.exit(1)\n }\n\n if (port !== requestedPort) {\n log.step(`Port ${requestedPort} in use, using ${port}`)\n }\n\n log.info(`Starting Blacksmith Studio for \"${config.name}\"...`)\n log.blank()\n\n try {\n const { createStudioServer } = await import('@blacksmith/studio')\n const { server } = await createStudioServer({ projectRoot: root, port })\n\n const url = `http://localhost:${port}`\n log.success('Blacksmith Studio is running!')\n log.blank()\n log.step(`Studio → ${url}`)\n log.step(`Project → ${root}`)\n log.blank()\n log.info('Press Ctrl+C to stop.')\n\n // Open browser\n try {\n const open = (await import('open')).default\n await open(url)\n } catch {\n // Silently ignore if open fails\n }\n\n const shutdown = () => {\n log.blank()\n log.info('Blacksmith Studio stopped.')\n server.close()\n process.exit(0)\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n } catch (error: any) {\n log.error(`Failed to start Studio: ${error.message}`)\n process.exit(1)\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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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,EAEb,OAAO,MAA4B;AACjC,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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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":["fs","fs","process","fs","process","Buffer","fs","promisify","childProcess","fs","fsConstants","execFile","powerShellPath","path","promisify","process","execFile","process","promisify","execFile","execFileAsync","promisify","execFile","execFileAsync","promisify","process","execFile","defaultBrowser","execFileAsync","process","process","path","fileURLToPath","childProcess","fs","fsConstants","__dirname","apps","defaultBrowser","powerShellPath","path","fs","path","path","fs","fileURLToPath","__filename","__dirname","path","fs","path","fs","path","fs","path","path","path","fs","path","fs","path","fs","path","fs","fs","path","path","fs","fs","fs","fs","path","fs","path","net","isPortAvailable","net","findAvailablePort","open"]}
|
|
1
|
+
{"version":3,"sources":["../node_modules/tsup/assets/esm_shims.js","../node_modules/is-docker/index.js","../node_modules/is-inside-container/index.js","../node_modules/is-wsl/index.js","../node_modules/powershell-utils/index.js","../node_modules/wsl-utils/utilities.js","../node_modules/wsl-utils/index.js","../node_modules/define-lazy-prop/index.js","../node_modules/default-browser-id/index.js","../node_modules/run-applescript/index.js","../node_modules/bundle-name/index.js","../node_modules/default-browser/windows.js","../node_modules/default-browser/index.js","../node_modules/is-in-ssh/index.js","../node_modules/open/index.js","../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/backend-modularization.ts","../src/skills/react.ts","../src/skills/react-query.ts","../src/skills/page-structure.ts","../src/skills/chakra-ui-react.ts","../src/skills/chakra-ui-forms.ts","../src/skills/chakra-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-modularization.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/mcp-setup.ts","../src/commands/studio.ts","../src/commands/backend.ts","../src/commands/frontend.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","import fs from 'node:fs';\n\nlet isDockerCached;\n\nfunction hasDockerEnv() {\n\ttry {\n\t\tfs.statSync('/.dockerenv');\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction hasDockerCGroup() {\n\ttry {\n\t\treturn fs.readFileSync('/proc/self/cgroup', 'utf8').includes('docker');\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nexport default function isDocker() {\n\t// TODO: Use `??=` when targeting Node.js 16.\n\tif (isDockerCached === undefined) {\n\t\tisDockerCached = hasDockerEnv() || hasDockerCGroup();\n\t}\n\n\treturn isDockerCached;\n}\n","import fs from 'node:fs';\nimport isDocker from 'is-docker';\n\nlet cachedResult;\n\n// Podman detection\nconst hasContainerEnv = () => {\n\ttry {\n\t\tfs.statSync('/run/.containerenv');\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n};\n\nexport default function isInsideContainer() {\n\t// TODO: Use `??=` when targeting Node.js 16.\n\tif (cachedResult === undefined) {\n\t\tcachedResult = hasContainerEnv() || isDocker();\n\t}\n\n\treturn cachedResult;\n}\n","import process from 'node:process';\nimport os from 'node:os';\nimport fs from 'node:fs';\nimport isInsideContainer from 'is-inside-container';\n\nconst isWsl = () => {\n\tif (process.platform !== 'linux') {\n\t\treturn false;\n\t}\n\n\tif (os.release().toLowerCase().includes('microsoft')) {\n\t\tif (isInsideContainer()) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\ttry {\n\t\tif (fs.readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft')) {\n\t\t\treturn !isInsideContainer();\n\t\t}\n\t} catch {}\n\n\t// Fallback for custom kernels: check WSL-specific paths.\n\tif (\n\t\tfs.existsSync('/proc/sys/fs/binfmt_misc/WSLInterop')\n\t\t|| fs.existsSync('/run/WSL')\n\t) {\n\t\treturn !isInsideContainer();\n\t}\n\n\treturn false;\n};\n\nexport default process.env.__IS_WSL_TEST__ ? isWsl : isWsl();\n","import process from 'node:process';\nimport {Buffer} from 'node:buffer';\nimport {promisify} from 'node:util';\nimport childProcess from 'node:child_process';\nimport fs, {constants as fsConstants} from 'node:fs/promises';\n\nconst execFile = promisify(childProcess.execFile);\n\nexport const powerShellPath = () => `${process.env.SYSTEMROOT || process.env.windir || String.raw`C:\\Windows`}\\\\System32\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe`;\n\n// Cache for PowerShell accessibility check\nlet canAccessCache;\n\nexport const canAccessPowerShell = async () => {\n\tcanAccessCache ??= (async () => {\n\t\ttry {\n\t\t\tawait fs.access(powerShellPath(), fsConstants.X_OK);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t})();\n\n\treturn canAccessCache;\n};\n\nexport const executePowerShell = async (command, options = {}) => {\n\tconst {\n\t\tpowerShellPath: psPath,\n\t\t...execFileOptions\n\t} = options;\n\n\tconst encodedCommand = executePowerShell.encodeCommand(command);\n\n\treturn execFile(\n\t\tpsPath ?? powerShellPath(),\n\t\t[\n\t\t\t...executePowerShell.argumentsPrefix,\n\t\t\tencodedCommand,\n\t\t],\n\t\t{\n\t\t\tencoding: 'utf8',\n\t\t\t...execFileOptions,\n\t\t},\n\t);\n};\n\nexecutePowerShell.argumentsPrefix = [\n\t'-NoProfile',\n\t'-NonInteractive',\n\t'-ExecutionPolicy',\n\t'Bypass',\n\t'-EncodedCommand',\n];\n\nexecutePowerShell.encodeCommand = command => Buffer.from(command, 'utf16le').toString('base64');\n\nexecutePowerShell.escapeArgument = value => `'${String(value).replaceAll('\\'', '\\'\\'')}'`;\n","export function parseMountPointFromConfig(content) {\n\tfor (const line of content.split('\\n')) {\n\t\t// Skip comment lines\n\t\tif (/^\\s*#/.test(line)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Match root at start of line (after optional whitespace)\n\t\tconst match = /^\\s*root\\s*=\\s*(?<mountPoint>\"[^\"]*\"|'[^']*'|[^#]*)/.exec(line);\n\t\tif (!match) {\n\t\t\tcontinue;\n\t\t}\n\n\t\treturn match.groups.mountPoint\n\t\t\t.trim()\n\t\t\t// Strip surrounding quotes\n\t\t\t.replaceAll(/^[\"']|[\"']$/g, '');\n\t}\n}\n","import {promisify} from 'node:util';\nimport childProcess from 'node:child_process';\nimport fs, {constants as fsConstants} from 'node:fs/promises';\nimport isWsl from 'is-wsl';\nimport {powerShellPath as windowsPowerShellPath, executePowerShell} from 'powershell-utils';\nimport {parseMountPointFromConfig} from './utilities.js';\n\nconst execFile = promisify(childProcess.execFile);\n\nexport const wslDrivesMountPoint = (() => {\n\t// Default value for \"root\" param\n\t// according to https://docs.microsoft.com/en-us/windows/wsl/wsl-config\n\tconst defaultMountPoint = '/mnt/';\n\n\tlet mountPoint;\n\n\treturn async function () {\n\t\tif (mountPoint) {\n\t\t\t// Return memoized mount point value\n\t\t\treturn mountPoint;\n\t\t}\n\n\t\tconst configFilePath = '/etc/wsl.conf';\n\n\t\tlet isConfigFileExists = false;\n\t\ttry {\n\t\t\tawait fs.access(configFilePath, fsConstants.F_OK);\n\t\t\tisConfigFileExists = true;\n\t\t} catch {}\n\n\t\tif (!isConfigFileExists) {\n\t\t\treturn defaultMountPoint;\n\t\t}\n\n\t\tconst configContent = await fs.readFile(configFilePath, {encoding: 'utf8'});\n\t\tconst parsedMountPoint = parseMountPointFromConfig(configContent);\n\n\t\tif (parsedMountPoint === undefined) {\n\t\t\treturn defaultMountPoint;\n\t\t}\n\n\t\tmountPoint = parsedMountPoint;\n\t\tmountPoint = mountPoint.endsWith('/') ? mountPoint : `${mountPoint}/`;\n\n\t\treturn mountPoint;\n\t};\n})();\n\nexport const powerShellPathFromWsl = async () => {\n\tconst mountPoint = await wslDrivesMountPoint();\n\treturn `${mountPoint}c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`;\n};\n\nexport const powerShellPath = isWsl ? powerShellPathFromWsl : windowsPowerShellPath;\n\n// Cache for PowerShell accessibility check\nlet canAccessPowerShellPromise;\n\nexport const canAccessPowerShell = async () => {\n\tcanAccessPowerShellPromise ??= (async () => {\n\t\ttry {\n\t\t\tconst psPath = await powerShellPath();\n\t\t\tawait fs.access(psPath, fsConstants.X_OK);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\t// PowerShell is not accessible (either doesn't exist, no execute permission, or other error)\n\t\t\treturn false;\n\t\t}\n\t})();\n\n\treturn canAccessPowerShellPromise;\n};\n\nexport const wslDefaultBrowser = async () => {\n\tconst psPath = await powerShellPath();\n\tconst command = String.raw`(Get-ItemProperty -Path \"HKCU:\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice\").ProgId`;\n\n\tconst {stdout} = await executePowerShell(command, {powerShellPath: psPath});\n\n\treturn stdout.trim();\n};\n\nexport const convertWslPathToWindows = async path => {\n\t// Don't convert URLs\n\tif (/^[a-z]+:\\/\\//i.test(path)) {\n\t\treturn path;\n\t}\n\n\ttry {\n\t\tconst {stdout} = await execFile('wslpath', ['-aw', path], {encoding: 'utf8'});\n\t\treturn stdout.trim();\n\t} catch {\n\t\t// If wslpath fails, return the original path\n\t\treturn path;\n\t}\n};\n\nexport {default as isWsl} from 'is-wsl';\n","export default function defineLazyProperty(object, propertyName, valueGetter) {\n\tconst define = value => Object.defineProperty(object, propertyName, {value, enumerable: true, writable: true});\n\n\tObject.defineProperty(object, propertyName, {\n\t\tconfigurable: true,\n\t\tenumerable: true,\n\t\tget() {\n\t\t\tconst result = valueGetter();\n\t\t\tdefine(result);\n\t\t\treturn result;\n\t\t},\n\t\tset(value) {\n\t\t\tdefine(value);\n\t\t}\n\t});\n\n\treturn object;\n}\n","import {promisify} from 'node:util';\nimport process from 'node:process';\nimport {execFile} from 'node:child_process';\n\nconst execFileAsync = promisify(execFile);\n\nexport default async function defaultBrowserId() {\n\tif (process.platform !== 'darwin') {\n\t\tthrow new Error('macOS only');\n\t}\n\n\tconst {stdout} = await execFileAsync('defaults', ['read', 'com.apple.LaunchServices/com.apple.launchservices.secure', 'LSHandlers']);\n\n\t// `(?!-)` is to prevent matching `LSHandlerRoleAll = \"-\";`.\n\tconst match = /LSHandlerRoleAll = \"(?!-)(?<id>[^\"]+?)\";\\s+?LSHandlerURLScheme = (?:http|https);/.exec(stdout);\n\n\tconst browserId = match?.groups.id ?? 'com.apple.Safari';\n\n\t// Correct the case for Safari's bundle identifier\n\tif (browserId === 'com.apple.safari') {\n\t\treturn 'com.apple.Safari';\n\t}\n\n\treturn browserId;\n}\n","import process from 'node:process';\nimport {promisify} from 'node:util';\nimport {execFile, execFileSync} from 'node:child_process';\n\nconst execFileAsync = promisify(execFile);\n\nexport async function runAppleScript(script, {humanReadableOutput = true, signal} = {}) {\n\tif (process.platform !== 'darwin') {\n\t\tthrow new Error('macOS only');\n\t}\n\n\tconst outputArguments = humanReadableOutput ? [] : ['-ss'];\n\n\tconst execOptions = {};\n\tif (signal) {\n\t\texecOptions.signal = signal;\n\t}\n\n\tconst {stdout} = await execFileAsync('osascript', ['-e', script, outputArguments], execOptions);\n\treturn stdout.trim();\n}\n\nexport function runAppleScriptSync(script, {humanReadableOutput = true} = {}) {\n\tif (process.platform !== 'darwin') {\n\t\tthrow new Error('macOS only');\n\t}\n\n\tconst outputArguments = humanReadableOutput ? [] : ['-ss'];\n\n\tconst stdout = execFileSync('osascript', ['-e', script, ...outputArguments], {\n\t\tencoding: 'utf8',\n\t\tstdio: ['ignore', 'pipe', 'ignore'],\n\t\ttimeout: 500,\n\t});\n\n\treturn stdout.trim();\n}\n","import {runAppleScript} from 'run-applescript';\n\nexport default async function bundleName(bundleId) {\n\treturn runAppleScript(`tell application \"Finder\" to set app_path to application file id \"${bundleId}\" as string\\ntell application \"System Events\" to get value of property list item \"CFBundleName\" of property list file (app_path & \":Contents:Info.plist\")`);\n}\n","import {promisify} from 'node:util';\nimport {execFile} from 'node:child_process';\n\nconst execFileAsync = promisify(execFile);\n\n// TODO: Fix the casing of bundle identifiers in the next major version.\n\n// Windows doesn't have browser IDs in the same way macOS/Linux does so we give fake\n// ones that look real and match the macOS/Linux versions for cross-platform apps.\nconst windowsBrowserProgIds = {\n\tMSEdgeHTM: {name: 'Edge', id: 'com.microsoft.edge'}, // The missing `L` is correct.\n\tMSEdgeBHTML: {name: 'Edge Beta', id: 'com.microsoft.edge.beta'},\n\tMSEdgeDHTML: {name: 'Edge Dev', id: 'com.microsoft.edge.dev'},\n\tAppXq0fevzme2pys62n3e0fbqa7peapykr8v: {name: 'Edge', id: 'com.microsoft.edge.old'},\n\tChromeHTML: {name: 'Chrome', id: 'com.google.chrome'},\n\tChromeBHTML: {name: 'Chrome Beta', id: 'com.google.chrome.beta'},\n\tChromeDHTML: {name: 'Chrome Dev', id: 'com.google.chrome.dev'},\n\tChromiumHTM: {name: 'Chromium', id: 'org.chromium.Chromium'},\n\tBraveHTML: {name: 'Brave', id: 'com.brave.Browser'},\n\tBraveBHTML: {name: 'Brave Beta', id: 'com.brave.Browser.beta'},\n\tBraveDHTML: {name: 'Brave Dev', id: 'com.brave.Browser.dev'},\n\tBraveSSHTM: {name: 'Brave Nightly', id: 'com.brave.Browser.nightly'},\n\tFirefoxURL: {name: 'Firefox', id: 'org.mozilla.firefox'},\n\tOperaStable: {name: 'Opera', id: 'com.operasoftware.Opera'},\n\tVivaldiHTM: {name: 'Vivaldi', id: 'com.vivaldi.Vivaldi'},\n\t'IE.HTTP': {name: 'Internet Explorer', id: 'com.microsoft.ie'},\n};\n\nexport const _windowsBrowserProgIdMap = new Map(Object.entries(windowsBrowserProgIds));\n\nexport class UnknownBrowserError extends Error {}\n\nexport default async function defaultBrowser(_execFileAsync = execFileAsync) {\n\tconst {stdout} = await _execFileAsync('reg', [\n\t\t'QUERY',\n\t\t' HKEY_CURRENT_USER\\\\Software\\\\Microsoft\\\\Windows\\\\Shell\\\\Associations\\\\UrlAssociations\\\\http\\\\UserChoice',\n\t\t'/v',\n\t\t'ProgId',\n\t]);\n\n\tconst match = /ProgId\\s*REG_SZ\\s*(?<id>\\S+)/.exec(stdout);\n\tif (!match) {\n\t\tthrow new UnknownBrowserError(`Cannot find Windows browser in stdout: ${JSON.stringify(stdout)}`);\n\t}\n\n\tconst {id} = match.groups;\n\n\t// Windows can append a hash suffix to ProgIds using a dot or hyphen\n\t// (e.g., `ChromeHTML.ABC123`, `FirefoxURL-6F193CCC56814779`).\n\t// Try exact match first, then try without the suffix.\n\tconst dotIndex = id.lastIndexOf('.');\n\tconst hyphenIndex = id.lastIndexOf('-');\n\tconst baseIdByDot = dotIndex === -1 ? undefined : id.slice(0, dotIndex);\n\tconst baseIdByHyphen = hyphenIndex === -1 ? undefined : id.slice(0, hyphenIndex);\n\n\treturn windowsBrowserProgIds[id] ?? windowsBrowserProgIds[baseIdByDot] ?? windowsBrowserProgIds[baseIdByHyphen] ?? {name: id, id};\n}\n","import {promisify} from 'node:util';\nimport process from 'node:process';\nimport {execFile} from 'node:child_process';\nimport defaultBrowserId from 'default-browser-id';\nimport bundleName from 'bundle-name';\nimport windows from './windows.js';\n\nexport {_windowsBrowserProgIdMap} from './windows.js';\n\nconst execFileAsync = promisify(execFile);\n\n// Inlined: https://github.com/sindresorhus/titleize/blob/main/index.js\nconst titleize = string => string.toLowerCase().replaceAll(/(?:^|\\s|-)\\S/g, x => x.toUpperCase());\n\nexport default async function defaultBrowser() {\n\tif (process.platform === 'darwin') {\n\t\tconst id = await defaultBrowserId();\n\t\tconst name = await bundleName(id);\n\t\treturn {name, id};\n\t}\n\n\tif (process.platform === 'linux') {\n\t\tconst {stdout} = await execFileAsync('xdg-mime', ['query', 'default', 'x-scheme-handler/http']);\n\t\tconst id = stdout.trim();\n\t\tconst name = titleize(id.replace(/.desktop$/, '').replace('-', ' '));\n\t\treturn {name, id};\n\t}\n\n\tif (process.platform === 'win32') {\n\t\treturn windows();\n\t}\n\n\tthrow new Error('Only macOS, Linux, and Windows are supported');\n}\n","import process from 'node:process';\n\nconst isInSsh = Boolean(process.env.SSH_CONNECTION\n\t|| process.env.SSH_CLIENT\n\t|| process.env.SSH_TTY);\n\nexport default isInSsh;\n","import process from 'node:process';\nimport path from 'node:path';\nimport {fileURLToPath} from 'node:url';\nimport childProcess from 'node:child_process';\nimport fs, {constants as fsConstants} from 'node:fs/promises';\nimport {\n\tisWsl,\n\tpowerShellPath,\n\tconvertWslPathToWindows,\n\tcanAccessPowerShell,\n\twslDefaultBrowser,\n} from 'wsl-utils';\nimport {executePowerShell} from 'powershell-utils';\nimport defineLazyProperty from 'define-lazy-prop';\nimport defaultBrowser, {_windowsBrowserProgIdMap} from 'default-browser';\nimport isInsideContainer from 'is-inside-container';\nimport isInSsh from 'is-in-ssh';\n\nconst fallbackAttemptSymbol = Symbol('fallbackAttempt');\n\n// Path to included `xdg-open`.\nconst __dirname = import.meta.url ? path.dirname(fileURLToPath(import.meta.url)) : '';\nconst localXdgOpenPath = path.join(__dirname, 'xdg-open');\n\nconst {platform, arch} = process;\n\nconst tryEachApp = async (apps, opener) => {\n\tif (apps.length === 0) {\n\t\t// No app was provided\n\t\treturn;\n\t}\n\n\tconst errors = [];\n\n\tfor (const app of apps) {\n\t\ttry {\n\t\t\treturn await opener(app); // eslint-disable-line no-await-in-loop\n\t\t} catch (error) {\n\t\t\terrors.push(error);\n\t\t}\n\t}\n\n\tthrow new AggregateError(errors, 'Failed to open in all supported apps');\n};\n\n// eslint-disable-next-line complexity\nconst baseOpen = async options => {\n\toptions = {\n\t\twait: false,\n\t\tbackground: false,\n\t\tnewInstance: false,\n\t\tallowNonzeroExitCode: false,\n\t\t...options,\n\t};\n\n\tconst isFallbackAttempt = options[fallbackAttemptSymbol] === true;\n\tdelete options[fallbackAttemptSymbol];\n\n\tif (Array.isArray(options.app)) {\n\t\treturn tryEachApp(options.app, singleApp => baseOpen({\n\t\t\t...options,\n\t\t\tapp: singleApp,\n\t\t\t[fallbackAttemptSymbol]: true,\n\t\t}));\n\t}\n\n\tlet {name: app, arguments: appArguments = []} = options.app ?? {};\n\tappArguments = [...appArguments];\n\n\tif (Array.isArray(app)) {\n\t\treturn tryEachApp(app, appName => baseOpen({\n\t\t\t...options,\n\t\t\tapp: {\n\t\t\t\tname: appName,\n\t\t\t\targuments: appArguments,\n\t\t\t},\n\t\t\t[fallbackAttemptSymbol]: true,\n\t\t}));\n\t}\n\n\tif (app === 'browser' || app === 'browserPrivate') {\n\t\t// IDs from default-browser for macOS and windows are the same.\n\t\t// IDs are lowercased to increase chances of a match.\n\t\tconst ids = {\n\t\t\t'com.google.chrome': 'chrome',\n\t\t\t'google-chrome.desktop': 'chrome',\n\t\t\t'com.brave.browser': 'brave',\n\t\t\t'org.mozilla.firefox': 'firefox',\n\t\t\t'firefox.desktop': 'firefox',\n\t\t\t'com.microsoft.msedge': 'edge',\n\t\t\t'com.microsoft.edge': 'edge',\n\t\t\t'com.microsoft.edgemac': 'edge',\n\t\t\t'microsoft-edge.desktop': 'edge',\n\t\t\t'com.apple.safari': 'safari',\n\t\t};\n\n\t\t// Incognito flags for each browser in `apps`.\n\t\tconst flags = {\n\t\t\tchrome: '--incognito',\n\t\t\tbrave: '--incognito',\n\t\t\tfirefox: '--private-window',\n\t\t\tedge: '--inPrivate',\n\t\t\t// Safari doesn't support private mode via command line\n\t\t};\n\n\t\tlet browser;\n\t\tif (isWsl) {\n\t\t\tconst progId = await wslDefaultBrowser();\n\t\t\tconst browserInfo = _windowsBrowserProgIdMap.get(progId);\n\t\t\tbrowser = browserInfo ?? {};\n\t\t} else {\n\t\t\tbrowser = await defaultBrowser();\n\t\t}\n\n\t\tif (browser.id in ids) {\n\t\t\tconst browserName = ids[browser.id.toLowerCase()];\n\n\t\t\tif (app === 'browserPrivate') {\n\t\t\t\t// Safari doesn't support private mode via command line\n\t\t\t\tif (browserName === 'safari') {\n\t\t\t\t\tthrow new Error('Safari doesn\\'t support opening in private mode via command line');\n\t\t\t\t}\n\n\t\t\t\tappArguments.push(flags[browserName]);\n\t\t\t}\n\n\t\t\treturn baseOpen({\n\t\t\t\t...options,\n\t\t\t\tapp: {\n\t\t\t\t\tname: apps[browserName],\n\t\t\t\t\targuments: appArguments,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tthrow new Error(`${browser.name} is not supported as a default browser`);\n\t}\n\n\tlet command;\n\tconst cliArguments = [];\n\tconst childProcessOptions = {};\n\n\t// Determine if we should use Windows/PowerShell behavior in WSL.\n\t// We only use Windows integration if PowerShell is actually accessible.\n\t// This allows the package to work in sandboxed WSL environments where Windows access is restricted.\n\tlet shouldUseWindowsInWsl = false;\n\tif (isWsl && !isInsideContainer() && !isInSsh && !app) {\n\t\tshouldUseWindowsInWsl = await canAccessPowerShell();\n\t}\n\n\tif (platform === 'darwin') {\n\t\tcommand = 'open';\n\n\t\tif (options.wait) {\n\t\t\tcliArguments.push('--wait-apps');\n\t\t}\n\n\t\tif (options.background) {\n\t\t\tcliArguments.push('--background');\n\t\t}\n\n\t\tif (options.newInstance) {\n\t\t\tcliArguments.push('--new');\n\t\t}\n\n\t\tif (app) {\n\t\t\tcliArguments.push('-a', app);\n\t\t}\n\t} else if (platform === 'win32' || shouldUseWindowsInWsl) {\n\t\tcommand = await powerShellPath();\n\n\t\tcliArguments.push(...executePowerShell.argumentsPrefix);\n\n\t\tif (!isWsl) {\n\t\t\tchildProcessOptions.windowsVerbatimArguments = true;\n\t\t}\n\n\t\t// Convert WSL Linux paths to Windows paths\n\t\tif (isWsl && options.target) {\n\t\t\toptions.target = await convertWslPathToWindows(options.target);\n\t\t}\n\n\t\t// Suppress PowerShell progress messages that are written to stderr\n\t\tconst encodedArguments = ['$ProgressPreference = \\'SilentlyContinue\\';', 'Start'];\n\n\t\tif (options.wait) {\n\t\t\tencodedArguments.push('-Wait');\n\t\t}\n\n\t\tif (app) {\n\t\t\tencodedArguments.push(executePowerShell.escapeArgument(app));\n\t\t\tif (options.target) {\n\t\t\t\tappArguments.push(options.target);\n\t\t\t}\n\t\t} else if (options.target) {\n\t\t\tencodedArguments.push(executePowerShell.escapeArgument(options.target));\n\t\t}\n\n\t\tif (appArguments.length > 0) {\n\t\t\tappArguments = appArguments.map(argument => executePowerShell.escapeArgument(argument));\n\t\t\tencodedArguments.push('-ArgumentList', appArguments.join(','));\n\t\t}\n\n\t\t// Using Base64-encoded command, accepted by PowerShell, to allow special characters.\n\t\toptions.target = executePowerShell.encodeCommand(encodedArguments.join(' '));\n\n\t\tif (!options.wait) {\n\t\t\t// PowerShell will keep the parent process alive unless stdio is ignored.\n\t\t\tchildProcessOptions.stdio = 'ignore';\n\t\t}\n\t} else {\n\t\tif (app) {\n\t\t\tcommand = app;\n\t\t} else {\n\t\t\t// When bundled by Webpack, there's no actual package file path and no local `xdg-open`.\n\t\t\tconst isBundled = !__dirname || __dirname === '/';\n\n\t\t\t// Check if local `xdg-open` exists and is executable.\n\t\t\tlet exeLocalXdgOpen = false;\n\t\t\ttry {\n\t\t\t\tawait fs.access(localXdgOpenPath, fsConstants.X_OK);\n\t\t\t\texeLocalXdgOpen = true;\n\t\t\t} catch {}\n\n\t\t\tconst useSystemXdgOpen = process.versions.electron\n\t\t\t\t?? (platform === 'android' || isBundled || !exeLocalXdgOpen);\n\t\t\tcommand = useSystemXdgOpen ? 'xdg-open' : localXdgOpenPath;\n\t\t}\n\n\t\tif (appArguments.length > 0) {\n\t\t\tcliArguments.push(...appArguments);\n\t\t}\n\n\t\tif (!options.wait) {\n\t\t\t// `xdg-open` will block the process unless stdio is ignored\n\t\t\t// and it's detached from the parent even if it's unref'd.\n\t\t\tchildProcessOptions.stdio = 'ignore';\n\t\t\tchildProcessOptions.detached = true;\n\t\t}\n\t}\n\n\tif (platform === 'darwin' && appArguments.length > 0) {\n\t\tcliArguments.push('--args', ...appArguments);\n\t}\n\n\t// IMPORTANT: On macOS, the target MUST come AFTER '--args'.\n\t// When using --args, ALL following arguments are passed to the app.\n\t// Example: open -a \"chrome\" --args --incognito https://site.com\n\t// This passes BOTH --incognito AND https://site.com to Chrome.\n\t// Without this order, Chrome won't open in incognito. See #332.\n\tif (options.target) {\n\t\tcliArguments.push(options.target);\n\t}\n\n\tconst subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);\n\n\tif (options.wait) {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tsubprocess.once('error', reject);\n\n\t\t\tsubprocess.once('close', exitCode => {\n\t\t\t\tif (!options.allowNonzeroExitCode && exitCode !== 0) {\n\t\t\t\t\treject(new Error(`Exited with code ${exitCode}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve(subprocess);\n\t\t\t});\n\t\t});\n\t}\n\n\t// When we're in a fallback attempt, we need to detect launch failures before trying the next app.\n\t// Wait for the close event to check the exit code before unreffing.\n\t// The launcher (open/xdg-open/PowerShell) exits quickly (~10-30ms) even on success.\n\tif (isFallbackAttempt) {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tsubprocess.once('error', reject);\n\n\t\t\tsubprocess.once('spawn', () => {\n\t\t\t\t// Keep error handler active for post-spawn errors\n\t\t\t\tsubprocess.once('close', exitCode => {\n\t\t\t\t\tsubprocess.off('error', reject);\n\n\t\t\t\t\tif (exitCode !== 0) {\n\t\t\t\t\t\treject(new Error(`Exited with code ${exitCode}`));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tsubprocess.unref();\n\t\t\t\t\tresolve(subprocess);\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\t}\n\n\tsubprocess.unref();\n\n\t// Handle spawn errors before the caller can attach listeners.\n\t// This prevents unhandled error events from crashing the process.\n\treturn new Promise((resolve, reject) => {\n\t\tsubprocess.once('error', reject);\n\n\t\t// Wait for the subprocess to spawn before resolving.\n\t\t// This ensures the process is established before the caller continues,\n\t\t// preventing issues when process.exit() is called immediately after.\n\t\tsubprocess.once('spawn', () => {\n\t\t\tsubprocess.off('error', reject);\n\t\t\tresolve(subprocess);\n\t\t});\n\t});\n};\n\nconst open = (target, options) => {\n\tif (typeof target !== 'string') {\n\t\tthrow new TypeError('Expected a `target`');\n\t}\n\n\treturn baseOpen({\n\t\t...options,\n\t\ttarget,\n\t});\n};\n\nexport const openApp = (name, options) => {\n\tif (typeof name !== 'string' && !Array.isArray(name)) {\n\t\tthrow new TypeError('Expected a valid `name`');\n\t}\n\n\tconst {arguments: appArguments = []} = options ?? {};\n\tif (appArguments !== undefined && appArguments !== null && !Array.isArray(appArguments)) {\n\t\tthrow new TypeError('Expected `appArguments` as Array type');\n\t}\n\n\treturn baseOpen({\n\t\t...options,\n\t\tapp: {\n\t\t\tname,\n\t\t\targuments: appArguments,\n\t\t},\n\t});\n};\n\nfunction detectArchBinary(binary) {\n\tif (typeof binary === 'string' || Array.isArray(binary)) {\n\t\treturn binary;\n\t}\n\n\tconst {[arch]: archBinary} = binary;\n\n\tif (!archBinary) {\n\t\tthrow new Error(`${arch} is not supported`);\n\t}\n\n\treturn archBinary;\n}\n\nfunction detectPlatformBinary({[platform]: platformBinary}, {wsl} = {}) {\n\tif (wsl && isWsl) {\n\t\treturn detectArchBinary(wsl);\n\t}\n\n\tif (!platformBinary) {\n\t\tthrow new Error(`${platform} is not supported`);\n\t}\n\n\treturn detectArchBinary(platformBinary);\n}\n\nexport const apps = {\n\tbrowser: 'browser',\n\tbrowserPrivate: 'browserPrivate',\n};\n\ndefineLazyProperty(apps, 'chrome', () => detectPlatformBinary({\n\tdarwin: 'google chrome',\n\twin32: 'chrome',\n\t// `chromium-browser` is the older deb package name used by Ubuntu/Debian before snap.\n\tlinux: ['google-chrome', 'google-chrome-stable', 'chromium', 'chromium-browser'],\n}, {\n\twsl: {\n\t\tia32: '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe',\n\t\tx64: ['/mnt/c/Program Files/Google/Chrome/Application/chrome.exe', '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe'],\n\t},\n}));\n\ndefineLazyProperty(apps, 'brave', () => detectPlatformBinary({\n\tdarwin: 'brave browser',\n\twin32: 'brave',\n\tlinux: ['brave-browser', 'brave'],\n}, {\n\twsl: {\n\t\tia32: '/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe',\n\t\tx64: ['/mnt/c/Program Files/BraveSoftware/Brave-Browser/Application/brave.exe', '/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe'],\n\t},\n}));\n\ndefineLazyProperty(apps, 'firefox', () => detectPlatformBinary({\n\tdarwin: 'firefox',\n\twin32: String.raw`C:\\Program Files\\Mozilla Firefox\\firefox.exe`,\n\tlinux: 'firefox',\n}, {\n\twsl: '/mnt/c/Program Files/Mozilla Firefox/firefox.exe',\n}));\n\ndefineLazyProperty(apps, 'edge', () => detectPlatformBinary({\n\tdarwin: 'microsoft edge',\n\twin32: 'msedge',\n\tlinux: ['microsoft-edge', 'microsoft-edge-dev'],\n}, {\n\twsl: '/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',\n}));\n\ndefineLazyProperty(apps, 'safari', () => detectPlatformBinary({\n\tdarwin: 'Safari',\n}));\n\nexport default open;\n","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 { setupMcp } from './commands/mcp-setup.js'\nimport { studio } from './commands/studio.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('--type <type>', 'Project type: fullstack, backend, or frontend (default: fullstack)')\n .option('--ai', 'Set up AI development skills and documentation (CLAUDE.md)')\n .option('--no-chakra-ui-skill', 'Disable Chakra 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-chakra-ui-skill', 'Exclude Chakra UI skill')\n .action(setupSkills)\n\nprogram\n .command('setup:mcp')\n .description('Configure MCP servers for Claude Code AI integration')\n .action(setupMcp)\n\nprogram\n .command('studio')\n .description('Launch Blacksmith Studio — web UI for Claude Code')\n .option('-p, --port <port>', 'Port for the Studio server (default: 3939)')\n .action(studio)\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, projectType: string = 'fullstack', backendPort?: number, frontendPort?: number) {\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 server' + (projectType === 'fullstack' ? 's' : ''))}`)\n console.log()\n\n if (backendPort !== undefined) {\n console.log(chalk.dim(` Django: http://localhost:${backendPort}`))\n console.log(chalk.dim(` Swagger: http://localhost:${backendPort}/api/docs/`))\n console.log(chalk.dim(` ReDoc: http://localhost:${backendPort}/api/redoc/`))\n }\n\n if (frontendPort !== undefined) {\n console.log(chalk.dim(` React: http://localhost:${frontendPort}`))\n }\n\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 type { ProjectType } 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']\nconst PROJECT_TYPES: ProjectType[] = ['fullstack', 'backend', 'frontend']\n\ninterface InitOptions {\n type?: string\n ai?: boolean\n chakraUiSkill?: 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 // Project type prompt\n let projectType: ProjectType\n if (options.type && PROJECT_TYPES.includes(options.type as ProjectType)) {\n projectType = options.type as ProjectType\n } else if (options.type) {\n log.error(`Invalid project type: \"${options.type}\". Must be one of: fullstack, backend, frontend`)\n process.exit(1)\n } else {\n projectType = await promptSelect('Project type', PROJECT_TYPES, 'fullstack') as ProjectType\n }\n\n const needsBackend = projectType === 'fullstack' || projectType === 'backend'\n const needsFrontend = projectType === 'fullstack' || projectType === 'frontend'\n\n if (needsBackend && !options.backendPort) {\n options.backendPort = await promptText('Backend port', '8000')\n }\n\n if (needsFrontend && !options.frontendPort) {\n options.frontendPort = await promptText('Frontend port', '5173')\n }\n\n if (needsFrontend && !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 = needsBackend ? parsePort(options.backendPort || '8000', 'backend') : undefined\n const frontendPort = needsFrontend ? parsePort(options.frontendPort || '5173', 'frontend') : undefined\n const themePreset = needsFrontend && options.themeColor && THEME_PRESETS.includes(options.themeColor)\n ? options.themeColor\n : 'default'\n\n const configDisplay: Record<string, string> = { 'Project': name, 'Type': projectType }\n if (needsBackend) configDisplay['Backend'] = `Django on :${backendPort}`\n if (needsFrontend) configDisplay['Frontend'] = `React on :${frontendPort}`\n if (needsFrontend) configDisplay['Theme'] = themePreset\n configDisplay['AI support'] = options.ai ? 'Yes' : 'No'\n printConfig(configDisplay)\n\n const projectDir = path.resolve(process.cwd(), name)\n const backendDir = needsBackend\n ? (projectType === 'backend' ? projectDir : path.join(projectDir, 'backend'))\n : null\n const frontendDir = needsFrontend\n ? (projectType === 'frontend' ? projectDir : path.join(projectDir, 'frontend'))\n : null\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\n if (needsBackend) {\n const hasPython = await commandExists('python3')\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\n if (needsFrontend) {\n const hasNode = await commandExists('node')\n const hasNpm = await commandExists('npm')\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\n const prereqs = [\n needsBackend ? 'Python 3' : null,\n needsFrontend ? 'Node.js, npm' : null,\n ].filter(Boolean).join(', ')\n checkSpinner.succeed(`Prerequisites OK (${prereqs})`)\n\n const context = {\n projectName: name,\n backendPort: backendPort || 8000,\n frontendPort: frontendPort || 5173,\n themePreset,\n }\n\n // 1. Create project directory and config\n fs.mkdirSync(projectDir, { recursive: true })\n\n const configObj: Record<string, unknown> = {\n name,\n version: '0.1.0',\n type: projectType,\n }\n if (needsBackend) configObj.backend = { port: backendPort }\n if (needsFrontend) configObj.frontend = { port: frontendPort }\n\n fs.writeFileSync(\n path.join(projectDir, 'blacksmith.config.json'),\n JSON.stringify(configObj, null, 2)\n )\n\n // 2. Generate backend\n if (backendDir) {\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\n // 6. Generate frontend\n if (frontendDir) {\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\n // 8. First OpenAPI sync (only for fullstack projects)\n if (backendDir && frontendDir) {\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\n // 10. AI development setup (opt-in)\n if (options.ai) {\n await setupAiDev({\n projectDir,\n projectName: name,\n includeChakraUiSkill: options.chakraUiSkill !== false,\n projectType,\n })\n }\n\n // 11. Print success\n printNextSteps(name, projectType, 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 project type from config (defaults to 'fullstack' for backward compatibility)\n */\nexport function getProjectType(projectRoot?: string): ProjectType {\n const config = loadConfig(projectRoot)\n return config.type || 'fullstack'\n}\n\n/**\n * Check if the project has a backend\n */\nexport function hasBackend(projectRoot?: string): boolean {\n const type = getProjectType(projectRoot)\n return type === 'fullstack' || type === 'backend'\n}\n\n/**\n * Check if the project has a frontend\n */\nexport function hasFrontend(projectRoot?: string): boolean {\n const type = getProjectType(projectRoot)\n return type === 'fullstack' || type === 'frontend'\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 const type = getProjectType(root)\n if (type === 'frontend') {\n throw new Error('This is a frontend-only project. There is no backend directory.')\n }\n return type === 'backend' ? root : 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 const type = getProjectType(root)\n if (type === 'backend') {\n throw new Error('This is a backend-only project. There is no frontend directory.')\n }\n return type === 'frontend' ? root : path.join(root, 'frontend')\n}\n\nexport type ProjectType = 'fullstack' | 'backend' | 'frontend'\n\nexport interface BlacksmithConfig {\n name: string\n version: string\n type?: ProjectType\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 type { ProjectType } from '../utils/paths.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 { backendModularizationSkill } from '../skills/backend-modularization.js'\nimport { reactSkill } from '../skills/react.js'\nimport { reactQuerySkill } from '../skills/react-query.js'\nimport { pageStructureSkill } from '../skills/page-structure.js'\nimport { chakraUiReactSkill } from '../skills/chakra-ui-react.js'\nimport { chakraUiFormsSkill } from '../skills/chakra-ui-forms.js'\nimport { chakraUiAuthSkill } from '../skills/chakra-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 { frontendModularizationSkill } from '../skills/frontend-modularization.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 includeChakraUiSkill: boolean\n projectType?: ProjectType\n}\n\nexport async function setupAiDev({ projectDir, projectName, includeChakraUiSkill, projectType = 'fullstack' }: AiSetupOptions) {\n const aiSpinner = spinner('Setting up AI development environment...')\n\n const needsBackend = projectType === 'fullstack' || projectType === 'backend'\n const needsFrontend = projectType === 'fullstack' || projectType === 'frontend'\n\n try {\n const skills: Skill[] = [\n coreRulesSkill,\n projectOverviewSkill,\n ]\n\n // Backend skills\n if (needsBackend) {\n skills.push(djangoSkill)\n skills.push(djangoRestAdvancedSkill)\n skills.push(apiDocumentationSkill)\n skills.push(backendModularizationSkill)\n }\n\n // Frontend skills\n if (needsFrontend) {\n skills.push(reactSkill)\n skills.push(reactQuerySkill)\n skills.push(pageStructureSkill)\n skills.push(frontendModularizationSkill)\n\n if (includeChakraUiSkill) {\n skills.push(chakraUiReactSkill)\n skills.push(chakraUiFormsSkill)\n skills.push(chakraUiAuthSkill)\n skills.push(blacksmithHooksSkill)\n skills.push(uiDesignSkill)\n }\n\n skills.push(frontendTestingSkill)\n }\n\n // Shared skills\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 \\`@chakra-ui/react\\` for ALL UI\n- **Layout**: Use \\`VStack\\`, \\`HStack\\`, \\`Flex\\`, \\`SimpleGrid\\`, \\`Box\\`, \\`Container\\` — NEVER \\`<div className=\"flex ...\">\\` or \\`<div className=\"grid ...\">\\`\n- **Typography**: Use \\`Heading\\` and \\`Text\\` — NEVER raw \\`<h1>\\`–\\`<h6>\\`, \\`<p>\\`, or \\`<span>\\` with text classes\n- **Separators**: Use \\`Divider\\` — NEVER \\`<hr>\\`\n- **Everything else**: \\`Button\\`, \\`Card\\`, \\`Badge\\`, \\`Input\\`, \\`Table\\`, \\`Modal\\`, \\`Alert\\`, \\`Skeleton\\`, \\`Stat\\`, etc.\n- **Exceptions**: Semantic HTML landmarks (\\`<main>\\`, \\`<section>\\`, \\`<nav>\\`, \\`<header>\\`, \\`<footer>\\`, \\`<article>\\`, \\`<aside>\\`) are acceptable for page structure. \\`<form>\\` is acceptable with React Hook Form. \\`<Link>\\` from react-router-dom for navigation\n- See the \\`chakra-ui-react\\` skill for the full component list\n\n### 2. Pages Are Thin Orchestrators\n- Pages import components, call hooks, and compose JSX — they should not contain business logic or large JSX blocks\n- Break every page into child components in a \\`components/\\` folder. Aim for clarity, not a strict line count\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. API Hooks vs UI Hooks — Two Different Places\n- **API hooks** (data fetching) → \\`src/api/hooks/<resource>/\\` — queries, mutations, cache invalidation. Import as: \\`import { usePosts } from '@/api/hooks/posts'\\`\n- **UI hooks** (page logic) → \\`pages/<page>/hooks/\\` or \\`features/<feature>/hooks/\\` — form state, pagination, filtering, debouncing\n- Never put API data fetching in page-level hooks. Never put UI-only logic in \\`src/api/hooks/\\`\n- See the \\`react-query\\` skill for API hook conventions\n\n### 6. Use Generated API Client Code\n- Always check \\`src/api/generated/\\` first before writing any API calls — use the generated types, query options, mutations, and query keys\n- Only write manual API client code when no generated code exists for the endpoint (e.g. the endpoint hasn't been synced yet)\n- **In fullstack projects:** after creating or modifying any backend endpoint (views, serializers, URLs), run \\`blacksmith sync\\` from the project root to regenerate the frontend API client before writing frontend code that consumes it\n\n### 7. Prioritize Modularization and Code Reuse\n- **Always reuse before writing** — before creating a new function, search the codebase for an existing one that does the same thing or can be extended\n- **Extract reusable logic to utils** — if a function can be useful outside the file it lives in, move it to a \\`utils/\\` folder. This applies to both frontend and backend\n - Frontend: page/feature-scoped → \\`<page>/utils/\\`; app-wide → \\`src/shared/utils/\\`\n - Backend: app-scoped → \\`apps/<app>/utils.py\\` (or \\`utils/\\` package); project-wide → \\`utils/\\` at the backend root\n- **No inline helper functions** — standalone functions sitting in component files, view files, or serializer files should be extracted to utils\n- **No duplicated logic** — if the same logic appears in two places, extract it into a shared utility immediately\n- See the \\`frontend-modularization\\` and \\`backend-modularization\\` skills for full conventions\n\n### 8. 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 only, 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 web application scaffolded by **Blacksmith CLI**. Check \\`blacksmith.config.json\\` at the project root for the project type (\\`fullstack\\`, \\`backend\\`, or \\`frontend\\`) and configuration.\n\n## Project Structure\n\nThe structure depends on the project type configured in \\`blacksmith.config.json\\`:\n\n**Fullstack** (\\`type: \"fullstack\"\\`) — Django backend + React frontend in subdirectories:\n\\`\\`\\`\n${ctx.projectName}/\n├── backend/ # Django project\n│ ├── apps/ # Django apps (one per resource)\n│ ├── config/ # Django settings, urls, wsgi/asgi\n│ ├── utils/ # Shared backend utilities\n│ ├── manage.py\n│ └── venv/ # Python virtual environment\n├── frontend/ # React + Vite project\n│ ├── src/\n│ │ ├── api/ # API client and hooks\n│ │ ├── features/ # Feature modules (auth, etc.)\n│ │ ├── pages/ # Top-level pages\n│ │ ├── router/ # React Router setup\n│ │ └── shared/ # Shared components, hooks, utils\n│ └── package.json\n├── blacksmith.config.json\n└── CLAUDE.md\n\\`\\`\\`\n\n**Backend-only** (\\`type: \"backend\"\\`) — Django project at root:\n\\`\\`\\`\n${ctx.projectName}/\n├── apps/\n├── config/\n├── utils/\n├── manage.py\n├── venv/\n└── blacksmith.config.json\n\\`\\`\\`\n\n**Frontend-only** (\\`type: \"frontend\"\\`) — React project at root:\n\\`\\`\\`\n${ctx.projectName}/\n├── src/\n│ ├── api/\n│ ├── pages/\n│ ├── router/\n│ └── shared/\n├── package.json\n└── blacksmith.config.json\n\\`\\`\\`\n\n## Commands\n\n| Command | Fullstack | Backend | Frontend |\n|---|---|---|---|\n| \\`blacksmith dev\\` | Django + Vite + sync | Django only | Vite only |\n| \\`blacksmith sync\\` | Regenerate frontend types | N/A | N/A |\n| \\`blacksmith make:resource <Name>\\` | Both ends | Backend only | Frontend only |\n| \\`blacksmith build\\` | Both | collectstatic | Vite build |\n| \\`blacksmith eject\\` | Remove Blacksmith | Remove Blacksmith | Remove Blacksmith |\n\n## Development Workflow\n\n**Fullstack:**\n1. Define models, serializers, and viewsets in the backend\n2. Run \\`blacksmith sync\\` to generate TypeScript types and API client\n3. Build frontend features using the generated hooks and types\n\n**Backend-only:**\n1. Define models, serializers, and viewsets\n2. Run migrations and test endpoints\n\n**Frontend-only:**\n1. Build pages and components\n2. Create API hooks in \\`src/api/hooks/\\` for data fetching\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### Sync After Backend Changes (Fullstack Projects)\n> **RULE: After creating or modifying any endpoint (views, serializers, URLs, or model fields that affect the API), run \\`blacksmith sync\\` from the project root before writing frontend code that uses the endpoint.**\n>\n> This regenerates the TypeScript types, query options, mutations, and Zod schemas in \\`frontend/src/api/generated/\\`. Without syncing, the frontend will be working against stale or missing type definitions.\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 backendModularizationSkill: Skill = {\n id: 'backend-modularization',\n name: 'Backend Modularization',\n description: 'When and how to promote Django modules (models, serializers, views, tests, admin) from single files to packages with per-class files.',\n\n render(_ctx: SkillContext): string {\n return `## Backend Modularization\n\n> **RULE: When a Django module contains more than one class, promote it from a single file to a package (folder) with one class per file and re-export everything from \\`__init__.py\\`.**\n\nThis applies to \\`models.py\\`, \\`serializers.py\\`, \\`views.py\\`, \\`admin.py\\`, and \\`tests.py\\`.\n\n### Single-Class Module — Keep as a File\n\nWhen an app has only one model, one serializer, etc., a single file is fine:\n\n\\`\\`\\`\napps/posts/\n├── __init__.py\n├── models.py # one model: Post\n├── serializers.py # one serializer: PostSerializer\n├── views.py # one viewset: PostViewSet\n├── urls.py\n├── admin.py\n└── tests.py\n\\`\\`\\`\n\n### Multi-Class Module — Promote to a Package\n\nWhen a second class is added, convert the file to a package:\n\n\\`\\`\\`\napps/posts/\n├── __init__.py\n├── models/\n│ ├── __init__.py # from .post import * / from .comment import *\n│ ├── post.py # class Post(models.Model)\n│ └── comment.py # class Comment(models.Model)\n├── serializers/\n│ ├── __init__.py # from .post_serializer import * / from .comment_serializer import *\n│ ├── post_serializer.py\n│ └── comment_serializer.py\n├── views/\n│ ├── __init__.py # from .post_views import * / from .comment_views import *\n│ ├── post_views.py\n│ └── comment_views.py\n├── admin/\n│ ├── __init__.py # from .post_admin import * / from .comment_admin import *\n│ ├── post_admin.py\n│ └── comment_admin.py\n├── tests/\n│ ├── __init__.py\n│ ├── test_post.py\n│ └── test_comment.py\n├── urls.py\n└── ...\n\\`\\`\\`\n\n### The \\`__init__.py\\` Contract\n\nEvery package \\`__init__.py\\` re-exports all public names so that imports from outside the app remain unchanged:\n\n\\`\\`\\`python\n# models/__init__.py\nfrom .post import *\nfrom .comment import *\n\\`\\`\\`\n\nThis means the rest of the codebase continues to use:\n\\`\\`\\`python\nfrom apps.posts.models import Post, Comment\nfrom apps.posts.serializers import PostSerializer, CommentSerializer\nfrom apps.posts.views import PostViewSet, CommentViewSet\n\\`\\`\\`\n\nNo import paths change when you promote a file to a package — that is the whole point.\n\n### File Naming Conventions\n\n| Module | Single file | Package file names |\n|---|---|---|\n| Models | \\`models.py\\` | \\`models/<model_name>.py\\` (e.g. \\`post.py\\`, \\`comment.py\\`) |\n| Serializers | \\`serializers.py\\` | \\`serializers/<model_name>_serializer.py\\` |\n| Views | \\`views.py\\` | \\`views/<model_name>_views.py\\` |\n| Admin | \\`admin.py\\` | \\`admin/<model_name>_admin.py\\` |\n| Tests | \\`tests.py\\` | \\`tests/test_<model_name>.py\\` |\n\n### Using \\`__all__\\` in Each File\n\nDefine \\`__all__\\` in each file to make the \\`from .module import *\\` explicit:\n\n\\`\\`\\`python\n# models/post.py\nfrom django.db import models\n\n__all__ = ['Post']\n\nclass Post(models.Model):\n title = models.CharField(max_length=255)\n content = models.TextField()\n created_at = models.DateTimeField(auto_now_add=True)\n\n def __str__(self):\n return self.title\n\\`\\`\\`\n\n\\`\\`\\`python\n# serializers/post_serializer.py\nfrom rest_framework import serializers\nfrom apps.posts.models import Post\n\n__all__ = ['PostSerializer']\n\nclass PostSerializer(serializers.ModelSerializer):\n class Meta:\n model = Post\n fields = '__all__'\n\\`\\`\\`\n\n### When to Promote\n\n- **Do promote** when a module contains 2 or more classes (models, serializers, viewsets, admin classes)\n- **Don't promote** \\`urls.py\\` — URL configuration is typically one file regardless of size, since routers naturally aggregate\n- **Don't promote** a module just because it is long — if it has one class with many methods, keep it as a file; length alone is not a reason\n- **Don't promote preemptively** — start with a single file and convert when the second class arrives\n\n### Migration Considerations\n\nWhen promoting \\`models.py\\` to a \\`models/\\` package, Django migrations continue to work as long as the import path in \\`__init__.py\\` is correct. Existing migrations reference the app label, not the file path, so no migration changes are needed.\n\n### Utility Functions\n\n> **RULE: Reuse before writing. If a function can be used outside the file it lives in, it belongs in utils.**\n\n- **App-scoped utilities** → \\`apps/<app>/utils.py\\` (or \\`utils/\\` package if multiple files)\n- **Project-wide utilities** → \\`utils/\\` at the backend root (e.g. \\`utils/formatting.py\\`, \\`utils/permissions.py\\`)\n\nBefore writing any helper function, search the codebase for an existing one. If the same logic exists in two places, extract it into a shared utility immediately.\n\n\\`\\`\\`python\n# BAD — helper function sitting in views.py\ndef parse_date_range(request):\n ...\n\nclass ReportViewSet(ModelViewSet):\n def get_queryset(self):\n start, end = parse_date_range(self.request)\n ...\n\n# GOOD — extracted to utils\n# apps/reports/utils.py\ndef parse_date_range(request):\n ...\n\n# apps/reports/views.py\nfrom apps.reports.utils import parse_date_range\n\\`\\`\\`\n\n\\`utils.py\\` follows the same promotion rule — when it has multiple unrelated groups of functions, promote it to a \\`utils/\\` package with focused files (\\`utils/dates.py\\`, \\`utils/formatting.py\\`, etc.) and re-export from \\`__init__.py\\`.\n\n### Summary\n\n1. **One class per file** when a module is promoted to a package\n2. **\\`__init__.py\\` re-exports everything** — external imports never change\n3. **Use \\`__all__\\`** in each file to be explicit about exports\n4. **Promote at 2+ classes** — not preemptively, not based on line count alone\n5. **\\`urls.py\\` stays a file** — it does not get promoted\n6. **Extract reusable functions to utils** — search before writing, no duplicated logic\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- Chakra UI v2 for component library and theming\n- Tailwind CSS for additional 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 \\`@chakra-ui/react\\` components** — see the \\`chakra-ui-react\\` skill for the full component list\n- Use \\`VStack\\`, \\`HStack\\`, \\`Flex\\`, \\`SimpleGrid\\`, \\`Box\\` for layout — never raw \\`<div>\\` with flex/grid classes\n- Use \\`Heading\\` and \\`Text\\` for headings and text — never raw \\`<h1>\\`–\\`<h6>\\` or \\`<p>\\`\n- Use \\`Divider\\` instead of \\`<hr>\\`\n- Use \\`Stat\\`, \\`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 Chakra UI style props as the primary styling approach\n- Use Tailwind CSS utility classes for additional styling needs\n- Theming via Chakra UI \\`extendTheme()\\` and design tokens\n- Color mode is supported via Chakra UI \\`useColorMode()\\` hook\n- Use responsive props (\\`{{ base: ..., md: ..., lg: ... }}\\`) for responsive layouts\n- Avoid inline \\`style\\` attributes — use Chakra style props or 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, useToast)\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\n> **RULE: All API hooks live in \\`src/api/hooks/<resource>/\\` — never in page-level \\`hooks/\\` folders.**\n> Page-level \\`hooks/\\` are for UI logic only (form state, filters, pagination). API data access is centralized in \\`src/api/hooks/\\`.\n\nWhen building hooks for a resource, create a folder under \\`src/api/hooks/\\` with these files:\n\n**\\`src/api/hooks/<resource>/index.ts\\`** — Re-exports all hooks from the folder.\n\n**\\`src/api/hooks/<resource>/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**\\`src/api/hooks/<resource>/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. **Always prefer generated API client code** — use the generated options, mutations, types, and query keys from \\`@/api/generated/\\`. Check \\`@/api/generated/@tanstack/react-query.gen\\` for available query options and mutations before writing anything manually. Only write a manual \\`queryFn\\` as a last resort when no generated code exists for the endpoint (e.g. the backend endpoint hasn't been synced yet)\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 @chakra-ui/react layout\nimport { VStack, SimpleGrid, Divider } from '@chakra-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 <VStack spacing={6} align=\"stretch\">\n <StatsCards stats={stats} isLoading={isLoading} />\n <Divider />\n <SimpleGrid columns={{ base: 1, lg: 3 }} spacing={6}>\n <RecentActivity items={activity} isLoading={isLoading} gridColumn={{ lg: 'span 2' }} />\n <QuickActions />\n </SimpleGrid>\n </VStack>\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 { VStack } from '@chakra-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 <VStack spacing={4} align=\"stretch\">\n <OrdersToolbar search={search} onSearchChange={setSearch} />\n <OrdersTable orders={orders} isLoading={isLoading} onDelete={(id) => deleteOrder.mutate({ path: { id } })} />\n </VStack>\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 chakraUiReactSkill: Skill = {\n id: 'chakra-ui-react',\n name: 'Chakra UI React',\n description: 'Core UI component library — Chakra UI v2 components for layout, typography, inputs, data display, overlays, feedback, media, and navigation.',\n\n render(_ctx: SkillContext): string {\n return `## Chakra UI React — Core UI Components\n\n> **CRITICAL RULE: Every UI element MUST be built using \\`@chakra-ui/react\\` components — including layout and typography.**\n> Do NOT use raw HTML elements when a Chakra UI component exists for that purpose.\n> This includes layout: use \\`HStack\\`, \\`VStack\\`, \\`SimpleGrid\\`, \\`Box\\`, \\`Container\\` instead of \\`<div>\\` with flex/grid classes.\n> This includes typography: use \\`Heading\\` and \\`Text\\` 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\\` / \\`HStack\\` | \\`<div className=\"flex ...\">\\` | Flexbox container with style props (\\`direction\\`, \\`align\\`, \\`justify\\`, \\`gap\\`, \\`wrap\\`) |\n| \\`SimpleGrid\\` | \\`<div className=\"grid ...\">\\` | CSS Grid container (\\`columns\\`, \\`spacing\\`) |\n| \\`VStack\\` | \\`<div className=\"flex flex-col gap-...\">\\` | Vertical stack (\\`spacing\\`) |\n| \\`HStack\\` | \\`<div className=\"flex flex-row gap-...\">\\` | Horizontal stack (\\`spacing\\`) |\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\n### Typography\n\n| Component | Use instead of | Description |\n|-----------|---------------|-------------|\n| \\`Text\\` | \\`<p>\\`, \\`<span>\\` | Text display with style props (\\`fontSize\\`, \\`fontWeight\\`, \\`color\\`, \\`textAlign\\`) |\n| \\`Heading\\` | \\`<h1>\\`–\\`<h6>\\`, \\`<p>\\` | Semantic heading elements (\\`as\\`: h1–h6, \\`size\\`: 2xl, xl, lg, md, sm, xs) |\n| \\`FormLabel\\` | \\`<label>\\` | Form label with accessibility support |\n\n### Cards & Containers\n\n- \\`Card\\`, \\`CardHeader\\`, \\`CardBody\\`, \\`CardFooter\\` — Use instead of styled \\`<div>\\` containers. Use \\`Heading\\` and \\`Text\\` inside for title/description.\n\n### Actions\n\n- \\`Button\\` — Use instead of \\`<button>\\` or \\`<a>\\` styled as buttons\n - Variants: \\`solid\\`, \\`outline\\`, \\`ghost\\`, \\`link\\`\n - Sizes: \\`xs\\`, \\`sm\\`, \\`md\\`, \\`lg\\`\n - Use \\`colorScheme\\` for color: \\`blue\\`, \\`red\\`, \\`green\\`, \\`gray\\`, etc.\n- \\`IconButton\\` — Use for icon-only buttons\n- \\`Menu\\`, \\`MenuButton\\`, \\`MenuList\\`, \\`MenuItem\\`, \\`MenuDivider\\`, \\`MenuGroup\\` — Use for action menus\n- \\`AlertDialog\\`, \\`AlertDialogOverlay\\`, \\`AlertDialogContent\\`, \\`AlertDialogHeader\\`, \\`AlertDialogBody\\`, \\`AlertDialogFooter\\` — Use for destructive action confirmations\n\n### Data Entry\n\n- \\`Input\\` — Use instead of \\`<input>\\`\n- \\`InputGroup\\`, \\`InputLeftElement\\`, \\`InputRightElement\\` — Use for input with icons/addons\n- \\`Textarea\\` — Use instead of \\`<textarea>\\`\n- \\`NumberInput\\`, \\`NumberInputField\\`, \\`NumberInputStepper\\`, \\`NumberIncrementStepper\\`, \\`NumberDecrementStepper\\` — Use for numeric inputs\n- \\`Select\\` — Use instead of \\`<select>\\`\n- \\`Checkbox\\`, \\`CheckboxGroup\\` — Use instead of \\`<input type=\"checkbox\">\\`\n- \\`Radio\\`, \\`RadioGroup\\` — Use instead of \\`<input type=\"radio\">\\`\n- \\`Switch\\` — Use for toggle switches\n- \\`Slider\\`, \\`SliderTrack\\`, \\`SliderFilledTrack\\`, \\`SliderThumb\\` — Use for range inputs\n- \\`PinInput\\`, \\`PinInputField\\` — Use for PIN/OTP code entry\n- \\`FormLabel\\` — Use instead of \\`<label>\\`\n\n### Data Display\n\n- \\`Table\\`, \\`Thead\\`, \\`Tbody\\`, \\`Tr\\`, \\`Th\\`, \\`Td\\`, \\`TableContainer\\` — Use instead of \\`<table>\\` elements\n- \\`Badge\\` — Use for status indicators, tags, counts (\\`colorScheme\\`: \\`green\\`, \\`red\\`, \\`blue\\`, \\`gray\\`, etc.)\n- \\`Avatar\\` — Use for user profile images (use \\`name\\` prop for fallback initials)\n- \\`Tooltip\\` — Use for hover hints (\\`label\\` prop for content)\n- \\`Stat\\`, \\`StatLabel\\`, \\`StatNumber\\`, \\`StatHelpText\\`, \\`StatArrow\\` — Use for metric/stat display\n- \\`Skeleton\\`, \\`SkeletonText\\`, \\`SkeletonCircle\\` — Use for loading placeholders\n- \\`Spinner\\` — Use for loading indicators\n- \\`Progress\\` — Use for progress bars\n- \\`Tag\\`, \\`TagLabel\\`, \\`TagCloseButton\\` — Use for removable tags\n\n### Tabs & Accordion\n\n- \\`Tabs\\`, \\`TabList\\`, \\`Tab\\`, \\`TabPanels\\`, \\`TabPanel\\` — Use for tabbed interfaces\n- \\`Accordion\\`, \\`AccordionItem\\`, \\`AccordionButton\\`, \\`AccordionPanel\\`, \\`AccordionIcon\\` — Use for collapsible sections\n\n### Overlays\n\n- \\`Modal\\`, \\`ModalOverlay\\`, \\`ModalContent\\`, \\`ModalHeader\\`, \\`ModalBody\\`, \\`ModalFooter\\`, \\`ModalCloseButton\\` — Use for modals\n- \\`AlertDialog\\` — Use for confirmation dialogs\n- \\`Drawer\\`, \\`DrawerOverlay\\`, \\`DrawerContent\\`, \\`DrawerHeader\\`, \\`DrawerBody\\`, \\`DrawerFooter\\`, \\`DrawerCloseButton\\` — Use for slide-out panels\n- \\`Popover\\`, \\`PopoverTrigger\\`, \\`PopoverContent\\`, \\`PopoverHeader\\`, \\`PopoverBody\\`, \\`PopoverArrow\\`, \\`PopoverCloseButton\\` — Use for floating content panels\n\n### Navigation\n\n- \\`Breadcrumb\\`, \\`BreadcrumbItem\\`, \\`BreadcrumbLink\\` — Use for breadcrumb trails\n\n### Feedback\n\n- \\`Alert\\`, \\`AlertIcon\\`, \\`AlertTitle\\`, \\`AlertDescription\\` — Use for inline messages/warnings (\\`status\\`: \\`error\\`, \\`warning\\`, \\`success\\`, \\`info\\`)\n- \\`useToast\\` — Use for transient notifications\n\n### Utilities & Hooks\n\n- \\`useColorMode()\\` — Color mode toggle. Returns \\`{ colorMode, toggleColorMode }\\`\n- \\`useDisclosure()\\` — Open/close state for modals, drawers, etc. Returns \\`{ isOpen, onOpen, onClose, onToggle }\\`\n- \\`useBreakpointValue()\\` — Responsive values based on breakpoint\n- \\`useToast()\\` — Programmatic toast notifications\n- \\`useMediaQuery()\\` — CSS media query matching\n\n---\n\n### Component-First Rules\n\n1. **Layout**: NEVER use \\`<div className=\"flex ...\">\\` or \\`<div className=\"grid ...\">\\`. Use \\`<Flex>\\`, \\`<SimpleGrid>\\`, \\`<VStack>\\`, \\`<HStack>\\`, \\`<Box>\\` from \\`@chakra-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 \\`<Heading as=\"h2\" size=\"lg\">\\` or \\`<Text>\\`.\n4. **Separators**: NEVER use \\`<hr>\\` or border hacks. Use \\`<Divider>\\`.\n5. **Buttons**: NEVER use \\`<button>\\` or \\`<a>\\` styled as a button. Use \\`<Button>\\`.\n6. **Inputs**: NEVER use \\`<input>\\`, \\`<textarea>\\`, \\`<select>\\` directly. Use the Chakra UI equivalents.\n7. **Cards**: NEVER use a styled \\`<div>\\` as a card. Use \\`Card\\` + sub-components.\n8. **Tables**: NEVER use raw \\`<table>\\` HTML. Use Chakra UI \\`Table\\` components.\n9. **Loading**: NEVER use custom \\`animate-pulse\\` divs. Use \\`Skeleton\\` or \\`Spinner\\`.\n10. **Modals**: NEVER build custom modals. Use \\`Modal\\`, \\`AlertDialog\\`, or \\`Drawer\\`.\n11. **Feedback**: NEVER use plain styled text for errors/warnings. Use \\`Alert\\` or \\`useToast\\`.\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\\`/\\`VStack\\`/\\`SimpleGrid\\` inside them for layout)\n- \\`<Link>\\` from react-router-dom — for page navigation (wrap with \\`<Button as={Link}>...\\` if it needs button styling)\n- Icon components from \\`lucide-react\\`\n- \\`<form>\\` element when used with React Hook Form (but use Chakra UI form components inside)\n\n### Design Tokens & Theming\n\n- \\`ChakraProvider\\` — Wrap app with \\`theme\\` prop to apply preset or custom theme\n- Extend theme with \\`extendTheme()\\` from \\`@chakra-ui/react\\`\n- All components use design tokens from the theme\n- Color mode: \\`useColorMode()\\` hook, or \\`ColorModeScript\\` in document head\n- Extend with \\`sx\\` prop or \\`style props\\` for custom styles\n\n### Example: HowItWorks Section (Correct Way)\n\n\\`\\`\\`tsx\nimport { Container, VStack, Flex, SimpleGrid, Text, Heading, Box } from '@chakra-ui/react'\nimport { howItWorksSteps } from '../data'\n\nexport function HowItWorks() {\n return (\n <Box as=\"section\" py={{ base: 16, sm: 20 }}>\n <Container maxW=\"container.xl\">\n <VStack spacing={3} align=\"center\" mb={12}>\n <Heading as=\"h2\" size=\"xl\">How It Works</Heading>\n <Text color=\"gray.500\">Book your stay in three simple steps</Text>\n </VStack>\n\n <SimpleGrid columns={{ base: 1, md: 3 }} spacing={8} maxW=\"4xl\" mx=\"auto\">\n {howItWorksSteps.map((item) => (\n <VStack key={item.step} align=\"center\" spacing={4}>\n <Box position=\"relative\">\n <Flex align=\"center\" justify=\"center\" h={16} w={16} rounded=\"full\" bg=\"blue.500\" color=\"white\" shadow=\"lg\">\n <item.icon size={28} />\n </Flex>\n <Flex align=\"center\" justify=\"center\" position=\"absolute\" top={-1} right={-1} h={6} w={6} rounded=\"full\" bg=\"white\" borderWidth={2} borderColor=\"blue.500\">\n <Text fontSize=\"xs\" fontWeight=\"bold\" color=\"blue.500\">{item.step}</Text>\n </Flex>\n </Box>\n <VStack spacing={2} align=\"center\">\n <Text fontSize=\"lg\" fontWeight=\"bold\">{item.title}</Text>\n <Text fontSize=\"sm\" color=\"gray.500\" textAlign=\"center\" maxW=\"xs\">\n {item.description}\n </Text>\n </VStack>\n </VStack>\n ))}\n </SimpleGrid>\n </Container>\n </Box>\n )\n}\n\\`\\`\\`\n\n### Example: Resource List Page (Correct Way)\n\n\\`\\`\\`tsx\nimport {\n VStack, Flex, Box,\n Card, CardHeader, CardBody,\n Button, Badge, Skeleton, Heading,\n Table, Thead, Tbody, Tr, Th, Td, TableContainer,\n Menu, MenuButton, MenuList, MenuItem, MenuDivider,\n AlertDialog, AlertDialogOverlay, AlertDialogContent,\n AlertDialogHeader, AlertDialogBody, AlertDialogFooter,\n IconButton, useDisclosure,\n} from '@chakra-ui/react'\nimport { MoreHorizontal, Plus, Trash2, Edit } from 'lucide-react'\nimport { Link } from 'react-router-dom'\nimport { useRef } from 'react'\n\nfunction ResourceListPage({ resources, isLoading, onDelete }) {\n const { isOpen, onOpen, onClose } = useDisclosure()\n const cancelRef = useRef()\n const [deleteId, setDeleteId] = useState(null)\n\n if (isLoading) {\n return (\n <Card>\n <CardBody p={6}>\n <VStack spacing={4}>\n {Array.from({ length: 5 }).map((_, i) => (\n <Skeleton key={i} h=\"48px\" w=\"full\" />\n ))}\n </VStack>\n </CardBody>\n </Card>\n )\n }\n\n return (\n <Card>\n <CardHeader>\n <Flex align=\"center\" justify=\"space-between\">\n <Heading size=\"md\">Resources</Heading>\n <Button as={Link} to=\"/resources/new\" leftIcon={<Plus size={16} />} colorScheme=\"blue\">\n Create\n </Button>\n </Flex>\n </CardHeader>\n <CardBody>\n <TableContainer>\n <Table>\n <Thead>\n <Tr>\n <Th>Title</Th>\n <Th>Status</Th>\n <Th w=\"48px\" />\n </Tr>\n </Thead>\n <Tbody>\n {resources.map((r) => (\n <Tr key={r.id}>\n <Td>{r.title}</Td>\n <Td><Badge variant=\"outline\">{r.status}</Badge></Td>\n <Td>\n <Menu>\n <MenuButton as={IconButton} icon={<MoreHorizontal size={16} />} variant=\"ghost\" size=\"sm\" />\n <MenuList>\n <MenuItem as={Link} to={\\`/resources/\\${r.id}/edit\\`} icon={<Edit size={16} />}>\n Edit\n </MenuItem>\n <MenuDivider />\n <MenuItem icon={<Trash2 size={16} />} color=\"red.500\" onClick={() => { setDeleteId(r.id); onOpen() }}>\n Delete\n </MenuItem>\n </MenuList>\n </Menu>\n </Td>\n </Tr>\n ))}\n </Tbody>\n </Table>\n </TableContainer>\n </CardBody>\n\n <AlertDialog isOpen={isOpen} leastDestructiveRef={cancelRef} onClose={onClose}>\n <AlertDialogOverlay>\n <AlertDialogContent>\n <AlertDialogHeader>Delete Resource</AlertDialogHeader>\n <AlertDialogBody>Are you sure? This cannot be undone.</AlertDialogBody>\n <AlertDialogFooter>\n <Button ref={cancelRef} onClick={onClose}>Cancel</Button>\n <Button colorScheme=\"red\" onClick={() => { onDelete(deleteId); onClose() }} ml={3}>\n Delete\n </Button>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialogOverlay>\n </AlertDialog>\n </Card>\n )\n}\n\\`\\`\\`\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const chakraUiFormsSkill: Skill = {\n id: 'chakra-ui-forms',\n name: 'Chakra UI Forms',\n description: 'Form components using Chakra UI + React Hook Form + Zod for validation and submission.',\n\n render(_ctx: SkillContext): string {\n return `## Chakra UI Forms — Form Components (React Hook Form + Zod)\n\n> **RULE: ALWAYS use Chakra UI form components for forms.** Do NOT build forms with raw \\`<form>\\`, \\`<input>\\`, \\`<label>\\`, or manual error display.\n\n\\`\\`\\`tsx\nimport {\n FormControl, FormLabel, FormErrorMessage, FormHelperText,\n Input, Textarea, Select, Checkbox, Switch, NumberInput,\n NumberInputField, NumberInputStepper, NumberIncrementStepper, NumberDecrementStepper,\n Radio, RadioGroup, Button,\n} from '@chakra-ui/react'\n\\`\\`\\`\n\n### Components\n\n- \\`FormControl\\` — Wraps each field. Props: \\`isInvalid\\`, \\`isRequired\\`, \\`isDisabled\\`\n- \\`FormLabel\\` — Label for a form field\n- \\`FormErrorMessage\\` — Displays field-level validation error (shown when \\`FormControl\\` has \\`isInvalid\\`)\n- \\`FormHelperText\\` — Displays helper text below a field\n- \\`Input\\` — Text input. Props: \\`type\\`, \\`placeholder\\`, \\`size\\`\n- \\`Textarea\\` — Textarea. Props: \\`rows\\`, \\`placeholder\\`\n- \\`Select\\` — Select dropdown. Props: \\`placeholder\\`\n- \\`Checkbox\\` — Checkbox input\n- \\`Switch\\` — Toggle switch\n- \\`RadioGroup\\` + \\`Radio\\` — Radio group\n- \\`NumberInput\\` — Numeric input with stepper\n\n### Rules\n- NEVER use raw \\`<form>\\` with manual \\`<label>\\` and error \\`<p>\\` tags. Always use \\`FormControl\\` + \\`FormLabel\\` + \\`FormErrorMessage\\`.\n- NEVER use \\`<input>\\`, \\`<textarea>\\`, \\`<select>\\` inside forms. Use Chakra UI \\`Input\\`, \\`Textarea\\`, \\`Select\\`.\n\n### Form Pattern — ALWAYS follow this:\n\\`\\`\\`tsx\nimport {\n FormControl, FormLabel, FormErrorMessage,\n Input, Textarea, Select, Button, VStack,\n} from '@chakra-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 { register, handleSubmit, formState: { errors } } = useForm<FormData>({\n resolver: zodResolver(schema),\n defaultValues: { title: '', description: '', status: 'draft', ...defaultValues },\n })\n\n return (\n <form onSubmit={handleSubmit(onSubmit)}>\n <VStack spacing={4} align=\"stretch\">\n <FormControl isInvalid={!!errors.title} isRequired>\n <FormLabel>Title</FormLabel>\n <Input placeholder=\"Enter title\" {...register('title')} />\n <FormErrorMessage>{errors.title?.message}</FormErrorMessage>\n </FormControl>\n\n <FormControl isInvalid={!!errors.description}>\n <FormLabel>Description</FormLabel>\n <Textarea rows={4} placeholder=\"Enter description\" {...register('description')} />\n <FormErrorMessage>{errors.description?.message}</FormErrorMessage>\n </FormControl>\n\n <FormControl isInvalid={!!errors.status}>\n <FormLabel>Status</FormLabel>\n <Select {...register('status')}>\n <option value=\"draft\">Draft</option>\n <option value=\"published\">Published</option>\n </Select>\n <FormErrorMessage>{errors.status?.message}</FormErrorMessage>\n </FormControl>\n\n <Button type=\"submit\" colorScheme=\"blue\" isLoading={isSubmitting}>\n Save\n </Button>\n </VStack>\n </form>\n )\n}\n\\`\\`\\`\n\n### Example: Detail Page with Edit Dialog\n\\`\\`\\`tsx\nimport {\n Card, CardHeader, CardBody, CardFooter,\n Button, Badge, Divider, Heading, Text,\n Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalFooter, ModalCloseButton,\n Alert, AlertIcon, AlertTitle, AlertDescription,\n FormControl, FormLabel, FormErrorMessage,\n Input, Textarea, Flex, HStack, useDisclosure,\n} from '@chakra-ui/react'\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 { isOpen, onOpen, onClose } = useDisclosure()\n const { register, handleSubmit, formState: { errors } } = useForm({\n resolver: zodResolver(editSchema),\n defaultValues: { title: resource.title, description: resource.description },\n })\n\n return (\n <Card>\n <CardHeader>\n <Flex align=\"center\" justify=\"space-between\">\n <Box>\n <Heading size=\"md\">{resource.title}</Heading>\n <Text fontSize=\"sm\" color=\"gray.500\">Created {new Date(resource.created_at).toLocaleDateString()}</Text>\n </Box>\n <HStack spacing={2}>\n <Button variant=\"outline\" as={Link} to=\"/resources\" leftIcon={<ArrowLeft size={16} />}>\n Back\n </Button>\n <Button leftIcon={<Edit size={16} />} colorScheme=\"blue\" onClick={onOpen}>\n Edit\n </Button>\n </HStack>\n </Flex>\n </CardHeader>\n <Divider />\n <CardBody>\n {error && (\n <Alert status=\"error\" mb={4}>\n <AlertIcon />\n <AlertTitle>Error</AlertTitle>\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n )}\n <Text>{resource.description || 'No description provided.'}</Text>\n </CardBody>\n <CardFooter>\n <Badge>{resource.status}</Badge>\n </CardFooter>\n\n <Modal isOpen={isOpen} onClose={onClose}>\n <ModalOverlay />\n <ModalContent>\n <ModalHeader>Edit Resource</ModalHeader>\n <ModalCloseButton />\n <form onSubmit={handleSubmit(onUpdate)}>\n <ModalBody>\n <VStack spacing={4}>\n <FormControl isInvalid={!!errors.title}>\n <FormLabel>Title</FormLabel>\n <Input {...register('title')} />\n <FormErrorMessage>{errors.title?.message}</FormErrorMessage>\n </FormControl>\n <FormControl isInvalid={!!errors.description}>\n <FormLabel>Description</FormLabel>\n <Textarea rows={4} {...register('description')} />\n <FormErrorMessage>{errors.description?.message}</FormErrorMessage>\n </FormControl>\n </VStack>\n </ModalBody>\n <ModalFooter>\n <Button type=\"submit\" colorScheme=\"blue\">Save Changes</Button>\n </ModalFooter>\n </form>\n </ModalContent>\n </Modal>\n </Card>\n )\n}\n\\`\\`\\`\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const chakraUiAuthSkill: Skill = {\n id: 'chakra-ui-auth',\n name: 'Custom Auth System',\n description: 'Custom authentication context, hooks, and types for login, registration, and password reset.',\n\n render(_ctx: SkillContext): string {\n return `## Custom Auth System — Authentication Context & Hooks\n\n> **RULE: Use the local AuthProvider and useAuth hook for all auth functionality.**\n> Auth components and pages are custom implementations in \\`src/features/auth/\\`.\n\n### Imports\n\n\\`\\`\\`tsx\nimport { useAuth } from '@/features/auth/hooks/use-auth'\nimport type { User } from '@/features/auth/types'\n\\`\\`\\`\n\n### useAuth() Hook\n\nReturns auth state and actions. Use this everywhere — never manage auth state manually.\n\n\\`\\`\\`tsx\nconst { user, isAuthenticated, loading, error, signInWithEmail, signOut } = useAuth()\n\\`\\`\\`\n\n| Field | Type | Description |\n|-------|------|-------------|\n| \\`user\\` | \\`User \\\\| null\\` | Current authenticated user |\n| \\`isAuthenticated\\` | \\`boolean\\` | Whether the user is logged in |\n| \\`loading\\` | \\`boolean\\` | Auth state is being resolved |\n| \\`error\\` | \\`string \\\\| null\\` | Last auth error message |\n| \\`signInWithEmail\\` | \\`(email, password) => Promise\\` | Log in |\n| \\`signUpWithEmail\\` | \\`(email, password, displayName?) => Promise\\` | Register |\n| \\`signOut\\` | \\`() => Promise\\` | Log out |\n| \\`sendPasswordResetEmail\\` | \\`(email) => Promise\\` | Request password reset |\n| \\`confirmPasswordReset\\` | \\`(code, newPassword) => Promise\\` | Set new password |\n\n### Usage Examples\n\n**Conditionally render based on auth:**\n\\`\\`\\`tsx\nimport { useAuth } from '@/features/auth/hooks/use-auth'\n\nfunction UserGreeting() {\n const { user, isAuthenticated } = useAuth()\n\n if (!isAuthenticated) return <Text>Please log in.</Text>\n return <Text>Welcome, {user?.email}</Text>\n}\n\\`\\`\\`\n\n**Protected action:**\n\\`\\`\\`tsx\nfunction LogoutButton() {\n const { signOut } = useAuth()\n\n return <Button onClick={signOut} variant=\"ghost\">Sign out</Button>\n}\n\\`\\`\\`\n\n**Login form integration:**\n\\`\\`\\`tsx\nimport { useAuth } from '@/features/auth/hooks/use-auth'\n\nfunction useLoginForm() {\n const { signInWithEmail, error } = useAuth()\n const navigate = useNavigate()\n\n const onSubmit = async (data: { email: string; password: string }) => {\n await signInWithEmail(data.email, data.password)\n navigate(Path.Dashboard)\n }\n\n return { onSubmit, error }\n}\n\\`\\`\\`\n\n### Auth Provider Setup\n\n\\`AuthProvider\\` wraps the app (inside \\`ChakraProvider\\`) and manages token storage and session lifecycle:\n\n\\`\\`\\`tsx\n// app.tsx\nimport { AuthProvider } from '@/features/auth/context'\n\nfunction App() {\n return (\n <ChakraProvider>\n <AuthProvider>\n <RouterProvider router={router} />\n </AuthProvider>\n </ChakraProvider>\n )\n}\n\\`\\`\\`\n\n### Auth Pages\n\nPre-built pages in \\`src/features/auth/pages/\\`:\n- \\`LoginPage\\` — email/password login with validation\n- \\`RegisterPage\\` — registration with email, password, display name\n- \\`ForgotPasswordPage\\` — password reset email request\n- \\`ResetPasswordPage\\` — set new password form\n\nAll use Chakra UI form components with React Hook Form + Zod validation.\n\n### Rules\n- NEVER manage auth state manually — always use \\`useAuth()\\`\n- Use the \\`Path\\` enum for auth route paths (\\`Path.Login\\`, \\`Path.Register\\`, etc.)\n- Auth adapter (Django JWT) lives in \\`src/features/auth/adapter.ts\\`\n`\n },\n}\n","import type { Skill, SkillContext } from './types.js'\n\nexport const blacksmithHooksSkill: Skill = {\n id: 'blacksmith-hooks',\n name: 'Custom Hooks & Chakra UI Hooks',\n description: 'Custom React hooks (scaffolded implementations) and Chakra UI built-in hooks for state, UI, layout, and responsiveness.',\n\n render(_ctx: SkillContext): string {\n return `## Custom Hooks & Chakra UI Hooks\n\n> **RULE: Use Chakra UI hooks when available, and project custom hooks for additional utilities.**\n> Before creating a new hook, check if one already exists in \\`src/shared/hooks/\\` or in Chakra UI.\n\n### Chakra UI Built-in Hooks\n\nThese are always available from \\`@chakra-ui/react\\`:\n\n| Hook | Description |\n|------|-------------|\n| \\`useColorMode\\` | Color mode toggle. Returns \\`{ colorMode, toggleColorMode, setColorMode }\\` |\n| \\`useColorModeValue\\` | Returns a value based on current color mode: \\`useColorModeValue(lightValue, darkValue)\\` |\n| \\`useDisclosure\\` | Open/close/toggle state for modals, drawers, popovers. Returns \\`{ isOpen, onOpen, onClose, onToggle }\\` |\n| \\`useBreakpointValue\\` | Responsive values: \\`useBreakpointValue({ base: 1, md: 2, lg: 3 })\\` |\n| \\`useMediaQuery\\` | CSS media query matching: \\`useMediaQuery('(min-width: 768px)')\\` |\n| \\`useToast\\` | Programmatic toast notifications |\n| \\`useClipboard\\` | Copy text to clipboard with status feedback |\n| \\`useBoolean\\` | Boolean state with \\`on\\`, \\`off\\`, \\`toggle\\` actions |\n| \\`useOutsideClick\\` | Detect clicks outside a ref element |\n| \\`useMergeRefs\\` | Merge multiple refs into one |\n| \\`useTheme\\` | Access the current Chakra UI theme object |\n\n### Scaffolded Project Hooks\n\nThese hooks are scaffolded by Blacksmith and live in \\`src/shared/hooks/\\`. Check the directory to see which ones are available in your project:\n\n| Hook | File | Description |\n|------|------|-------------|\n| \\`useDebounce\\` | \\`use-debounce.ts\\` | Debounce a value with configurable delay |\n| \\`useApiQuery\\` | \\`use-api-query.ts\\` | Wrapper around \\`useQuery\\` with DRF error parsing and smart retry |\n| \\`useApiMutation\\` | \\`use-api-mutation.ts\\` | Wrapper around \\`useMutation\\` with DRF error parsing and cache invalidation |\n\n> **Note:** Additional hooks (e.g. \\`useLocalStorage\\`, \\`usePrevious\\`, \\`useInterval\\`) can be created as needed in \\`src/shared/hooks/\\`. Always check if one exists before writing a new one.\n\n### Common Patterns\n\n**Modal with Chakra disclosure:**\n\\`\\`\\`tsx\nimport { useDisclosure } from '@chakra-ui/react'\nimport { Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalCloseButton, Button } from '@chakra-ui/react'\n\nfunction MyComponent() {\n const { isOpen, onOpen, onClose } = useDisclosure()\n\n return (\n <>\n <Button onClick={onOpen}>Open</Button>\n <Modal isOpen={isOpen} onClose={onClose}>\n <ModalOverlay />\n <ModalContent>\n <ModalHeader>Modal Title</ModalHeader>\n <ModalCloseButton />\n <ModalBody>Modal content here</ModalBody>\n </ModalContent>\n </Modal>\n </>\n )\n}\n\\`\\`\\`\n\n**Debounced search:**\n\\`\\`\\`tsx\nimport { useDebounce } from '@/shared/hooks/use-debounce'\nimport { Input } from '@chakra-ui/react'\n\nfunction SearchBar({ onSearch }: { onSearch: (q: string) => void }) {\n const [query, setQuery] = useState('')\n const debouncedQuery = useDebounce(query, 300)\n\n useEffect(() => { onSearch(debouncedQuery) }, [debouncedQuery, onSearch])\n\n return <Input value={query} onChange={(e) => setQuery(e.target.value)} placeholder=\"Search...\" />\n}\n\\`\\`\\`\n\n**Responsive layout:**\n\\`\\`\\`tsx\nimport { useBreakpointValue } from '@chakra-ui/react'\n\nfunction Layout({ children }) {\n const columns = useBreakpointValue({ base: 1, md: 2, lg: 3 })\n return <SimpleGrid columns={columns} spacing={6}>{children}</SimpleGrid>\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 development server(s) |\n| \\`blacksmith sync\\` | Regenerate frontend API client from Django OpenAPI schema (fullstack only) |\n| \\`blacksmith make:resource <Name>\\` | Scaffold a resource (backend, frontend, or both depending on project type) |\n| \\`blacksmith build\\` | Production build |\n| \\`blacksmith eject\\` | Remove Blacksmith dependency, keep a clean project |\n| \\`blacksmith setup:ai\\` | Generate/regenerate 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 \"type\": \"fullstack\",\n \"backend\": { \"port\": 8000 },\n \"frontend\": { \"port\": 5173 }\n}\n\\`\\`\\`\n\n- **\\`type\\`** — \\`\"fullstack\"\\`, \\`\"backend\"\\`, or \\`\"frontend\"\\`. Determines which commands and steps are available\n- **\\`backend\\`** — present for fullstack and backend projects. Absent for frontend-only\n- **\\`frontend\\`** — present for fullstack and frontend projects. Absent for backend-only\n- **Ports** are read by \\`blacksmith dev\\` — 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\nDepends on project type:\n- **Fullstack**: Runs three concurrent processes — Django, Vite, and an OpenAPI file watcher (auto-syncs types on \\`.py\\` changes)\n- **Backend**: Runs Django only\n- **Frontend**: Runs Vite only\n\nAll processes 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 scaffolds based on project type:\n\n**Backend (fullstack and backend projects):**\n- \\`apps/blog_posts/\\` — model, serializer, viewset, urls, admin, tests\n- Wires the app into \\`INSTALLED_APPS\\` and \\`config/urls.py\\`\n- Runs \\`makemigrations\\` and \\`migrate\\`\n\n**Frontend (fullstack and frontend projects):**\n- \\`src/api/hooks/blog-posts/\\` — query and mutation hooks\n- \\`src/pages/blog-posts/\\` — list and detail pages\n- Registers route path in \\`src/router/paths.ts\\` and routes in \\`src/router/routes.tsx\\`\n\n**Fullstack only:** Also runs \\`blacksmith sync\\` to generate the TypeScript API client.\n\n### How \\`blacksmith sync\\` Works (Fullstack Only)\n\n1. Generates the OpenAPI schema offline using \\`manage.py spectacular\\`\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 --type fullstack -b 9000 -f 3000 --ai\n\\`\\`\\`\n\n| Flag | Description |\n|---|---|\n| \\`--type <type>\\` | Project type: fullstack, backend, or frontend (default: fullstack) |\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-chakra-ui-skill\\` | Exclude Chakra 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: 'Design principles, spacing, typography, color, layout patterns, and interaction guidelines aligned with the Chakra UI 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> Follow a design language similar to Linear, Vercel, and Stripe — minimal chrome, generous whitespace, subtle depth, and purposeful motion.\n\n### Core Principles\n\n1. **Flat over skeuomorphic** — No gradients on surfaces, no heavy drop shadows, no bevels. Solid colors, subtle borders, and \\`shadow=\"sm\"\\` / \\`shadow=\"md\"\\` only where elevation is meaningful (cards, dropdowns, modals)\n2. **Content over decoration** — UI presents content, not decoration. 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. When in doubt, add more space\n4. **Consistency over creativity** — Every page should feel like part of the same app. Same spacing, same patterns, same interactions\n5. **Progressive disclosure** — Show only what's needed. Use tabs, dialogs, and drill-down navigation to manage complexity\n\n### Spacing\n\nUse Chakra UI's numeric spacing scale consistently (1 = 4px):\n\n| Scale | Use for |\n|-------|---------|\n| \\`1\\`–\\`2\\` | Icon-to-text gaps, tight badge padding |\n| \\`3\\`–\\`4\\` | Inner component padding, gap between related items |\n| \\`5\\`–\\`6\\` | Card padding, section inner spacing |\n| \\`8\\` | Gap between sections within a page |\n| \\`10\\`–\\`12\\` | Gap between major page sections |\n\n- Use \\`VStack spacing={...}\\` / \\`HStack spacing={...}\\` for sibling spacing — not margin on individual items\n- Page content: use \\`Container\\` for responsive horizontal padding\n- Card body: \\`p={6}\\` standard, \\`p={4}\\` for compact cards\n\n### Typography\n\n| Level | Component | Use for |\n|-------|-----------|---------|\n| Page title | \\`<Heading as=\"h1\" size=\"2xl\">\\` | One per page |\n| Section title | \\`<Heading as=\"h2\" size=\"xl\">\\` | Major sections |\n| Sub-section | \\`<Heading as=\"h3\" size=\"lg\">\\` | Groups within a section |\n| Card title | \\`<Heading as=\"h4\" size=\"md\">\\` | Card headings |\n| Body | \\`<Text>\\` | Paragraphs, descriptions |\n| Caption | \\`<Text fontSize=\"sm\" color=\"gray.500\">\\` | Metadata, timestamps |\n\n- One \\`h1\\` per page. Never skip heading levels\n- Max reading width: \\`maxW=\"prose\"\\` for long-form text\n- Use \\`useColorModeValue('gray.600', 'gray.400')\\` for secondary text\n\n### Color\n\nUse Chakra UI theme tokens — never hardcoded hex/rgb values:\n\n| colorScheme | Usage |\n|-------------|-------|\n| \\`blue\\` | Primary actions, active states |\n| \\`gray\\` | Secondary actions, subtle backgrounds |\n| \\`red\\` | Delete, error, danger |\n| \\`green\\` | Success states |\n| \\`yellow\\` / \\`orange\\` | Warning states |\n\n- Use \\`useColorModeValue()\\` for any color that must adapt between light and dark mode\n- Maximum 2–3 colors visible at once. Colorful UIs feel noisy\n- Status indicators: use \\`Badge\\` with \\`colorScheme\\` — don't hand-roll colored pills\n\n### Dark Mode\n\n> **Every screen and component MUST render correctly in both light and dark mode.**\n\n- Use \\`useColorModeValue()\\` for any custom colors\n- NEVER hardcode \\`bg=\"white\"\\` or \\`bg=\"black\"\\`. Use \\`bg={useColorModeValue('white', 'gray.800')}\\`\n- All Chakra UI components automatically adapt — leverage this\n\n### Interactions & Feedback\n\n- **Loading**: \\`Skeleton\\` for content areas, \\`isLoading\\` prop on buttons. Never leave the user without feedback\n- **Success/error**: \\`useToast()\\` for transient confirmations, \\`Alert\\` for persistent messages. Never \\`window.alert()\\`\n- **Destructive actions**: Always use \\`AlertDialog\\` for delete/remove. Never delete on single click\n- **Touch targets**: Minimum 44x44px for interactive elements on mobile\n\n### Responsive Design\n\n- **Mobile-first**: Chakra responsive props use mobile-first breakpoints (\\`base\\`, \\`sm\\`, \\`md\\`, \\`lg\\`, \\`xl\\`)\n- **Responsive props**: \\`columns={{ base: 1, md: 2, lg: 3 }}\\`\n- **Stack direction**: \\`Stack direction={{ base: 'column', md: 'row' }}\\` for responsive stacking\n- Always wrap page content in \\`<Container>\\` for responsive horizontal padding\n\nFor the full component reference, see the \\`chakra-ui-react\\` skill.\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 frontendModularizationSkill: Skill = {\n id: 'frontend-modularization',\n name: 'Frontend Modularization',\n description: 'Component file structure, one-component-per-file rule, folder-based components, utility extraction, and barrel exports.',\n\n render(_ctx: SkillContext): string {\n return `## Frontend Modularization\n\n> **RULE: One component per file. Extract non-component functions to utils. Promote complex components to folders with barrel exports.**\n\n### One Component Per File\n\nEvery \\`.tsx\\` file exports exactly one React component. No sibling components, no inline helpers that render JSX.\n\n\\`\\`\\`tsx\n// BAD — two components in one file\nexport function UserCard({ user }: Props) { ... }\nexport function UserCardSkeleton() { ... }\n\n// GOOD — separate files\n// user-card.tsx\nexport function UserCard({ user }: Props) { ... }\n\n// user-card-skeleton.tsx\nexport function UserCardSkeleton() { ... }\n\\`\\`\\`\n\nSmall, tightly-coupled sub-components (e.g. a skeleton for a card) still get their own file — co-locate them in the same directory.\n\n### Utility Functions Live in Utils\n\n> **RULE: Reuse before writing. Before creating any helper function, search the codebase for an existing one. If a function can be used outside the file it lives in, it belongs in utils.**\n\nFunctions that are not components or hooks do not belong in component files. Extract them:\n\n- **Page/feature-scoped utilities** → \\`utils/\\` folder next to the component\n- **App-wide utilities** → \\`src/shared/utils/\\`\n\nIf the same logic exists in two places, extract it into a shared utility immediately — never duplicate.\n\n\\`\\`\\`tsx\n// BAD — helper function sitting in the component file\nfunction formatCurrency(amount: number) {\n return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount)\n}\n\nexport function PriceTag({ amount }: { amount: number }) {\n return <Text>{formatCurrency(amount)}</Text>\n}\n\n// GOOD — utility extracted\n// utils/format.ts\nexport function formatCurrency(amount: number) {\n return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount)\n}\n\n// price-tag.tsx\nimport { formatCurrency } from '../utils/format'\n\nexport function PriceTag({ amount }: { amount: number }) {\n return <Text>{formatCurrency(amount)}</Text>\n}\n\\`\\`\\`\n\n**Exception:** Tiny, one-line helpers used only in that file (e.g. a local type guard) can stay inline. If it grows beyond a few lines or is used elsewhere, extract it.\n\n### Simple Components — Single File\n\nA component that is self-contained and has no children components stays as a single file:\n\n\\`\\`\\`\ncomponents/\n├── user-avatar.tsx\n├── status-badge.tsx\n└── empty-state.tsx\n\\`\\`\\`\n\n### Complex Components — Folder with Barrel Export\n\nWhen a component grows to have its own child components or hooks, promote it to a folder:\n\n\\`\\`\\`\ncomponents/\n└── data-table/\n ├── index.ts # Barrel — re-exports the main component\n ├── data-table.tsx # Main component (the orchestrator)\n ├── components/\n │ ├── table-header.tsx # Child component\n │ ├── table-row.tsx # Child component\n │ ├── table-pagination.tsx # Child component\n │ └── table-empty-state.tsx # Child component\n ├── hooks/\n │ ├── use-table-sorting.ts # Sorting logic\n │ └── use-table-filters.ts # Filter logic\n └── utils/\n └── column-helpers.ts # Non-component, non-hook helpers\n\\`\\`\\`\n\n**\\`index.ts\\`** — barrel export for the main component:\n\\`\\`\\`ts\nexport { DataTable } from './data-table'\nexport type { DataTableProps } from './data-table'\n\\`\\`\\`\n\nConsumers import from the folder, not the internal file:\n\\`\\`\\`tsx\n// GOOD\nimport { DataTable } from '@/shared/components/data-table'\n\n// BAD — reaching into internal structure\nimport { DataTable } from '@/shared/components/data-table/data-table'\n\\`\\`\\`\n\n### When to Promote a File to a Folder\n\nPromote a single-file component to a folder when any of these apply:\n\n1. It has **child components** — sections of JSX that are large enough to extract\n2. It has **custom hooks** — local logic that warrants its own file\n3. It has **utility functions** — helpers that should live in \\`utils/\\`\n4. It exceeds **~150 lines** — a sign it needs decomposition\n\n### Folder Structure Rules\n\n| What | Where |\n|---|---|\n| Child components only used by the parent | \\`<component>/components/\\` |\n| UI logic hooks for the component | \\`<component>/hooks/\\` |\n| Non-component, non-hook helpers | \\`<component>/utils/\\` |\n| Main component | \\`<component>/<component>.tsx\\` |\n| Public API | \\`<component>/index.ts\\` (barrel) |\n\n### Barrel Export Rules\n\n- The \\`index.ts\\` only re-exports the main component and its public types\n- Child components in \\`components/\\` are private — they are **not** re-exported\n- If a child component needs to be used elsewhere, move it up to the parent \\`components/\\` directory or to \\`shared/components/\\`\n\n\\`\\`\\`ts\n// GOOD — index.ts re-exports only the public API\nexport { DataTable } from './data-table'\nexport type { DataTableProps, Column } from './data-table'\n\n// BAD — leaking internal child components\nexport { DataTable } from './data-table'\nexport { TableHeader } from './components/table-header'\nexport { TableRow } from './components/table-row'\n\\`\\`\\`\n\n### Where Components Live\n\n| Scope | Location |\n|---|---|\n| Used across the entire app | \\`src/shared/components/\\` |\n| Used only within a feature | \\`src/features/<feature>/components/\\` |\n| Used only within a page | \\`src/pages/<page>/components/\\` |\n| Used only within a parent component | \\`<parent>/components/\\` |\n\n### Summary\n\n1. **One component per file** — always\n2. **Non-component functions go in \\`utils/\\`** — never loose functions in component files\n3. **Reuse before writing** — search the codebase for existing utils before creating new ones; no duplicated logic\n4. **Simple component = single file** — no folder overhead for leaf components\n5. **Complex component = folder** with \\`components/\\`, \\`hooks/\\`, \\`utils/\\` subdirectories and an \\`index.ts\\` barrel\n6. **Barrel exports are the public API** — consumers import from the folder, never reach into internals\n7. **Scope determines location** — shared, feature, page, or parent component level\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### Test Rendering Setup\n\n> **RULE: Never import \\`render\\` from \\`@testing-library/react\\` directly. Create a \\`renderWithProviders\\` helper in \\`src/__tests__/test-utils.tsx\\` that wraps components with all app providers.**\n\nCreate \\`src/__tests__/test-utils.tsx\\` if it doesn't exist:\n\n\\`\\`\\`tsx\n// src/__tests__/test-utils.tsx\nimport { render, type RenderOptions } from '@testing-library/react'\nimport { ChakraProvider } from '@chakra-ui/react'\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query'\nimport { MemoryRouter } from 'react-router-dom'\nimport userEvent from '@testing-library/user-event'\n\ninterface ProviderOptions extends Omit<RenderOptions, 'wrapper'> {\n routerEntries?: string[]\n queryClient?: QueryClient\n}\n\nexport function renderWithProviders(ui: React.ReactElement, options: ProviderOptions = {}) {\n const { routerEntries = ['/'], queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }), ...renderOptions } = options\n const user = userEvent.setup()\n\n const result = render(ui, {\n wrapper: ({ children }) => (\n <ChakraProvider>\n <QueryClientProvider client={queryClient}>\n <MemoryRouter initialEntries={routerEntries}>{children}</MemoryRouter>\n </QueryClientProvider>\n </ChakraProvider>\n ),\n ...renderOptions,\n })\n\n return { ...result, user }\n}\n\nexport { screen, waitFor, within } from '@testing-library/react'\n\\`\\`\\`\n\n**Usage:**\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**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 auth hook (for auth page tests):**\n\\`\\`\\`tsx\nvi.mock('@/features/auth/hooks/use-auth', () => ({\n useAuth: () => ({\n user: null,\n loading: false,\n error: null,\n isAuthenticated: false,\n signInWithEmail: vi.fn(),\n signOut: vi.fn(),\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 } 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 \\`@chakra-ui/react\\` layout components (\\`VStack\\`, \\`HStack\\`, \\`Flex\\`, \\`SimpleGrid\\`) — 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 the appropriate files based on project type\n2. **Fullstack projects:** After any backend API change (new endpoint, changed schema, new field), run \\`blacksmith sync\\` from the project root to regenerate the frontend API client and types\n3. Never manually edit files in \\`src/api/generated/\\` — they are overwritten on every sync\n4. Before writing any new function, search the codebase for an existing one that does the same thing\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 \\`@chakra-ui/react\\` for ALL UI** — \\`VStack\\`, \\`HStack\\`, \\`Flex\\`, \\`SimpleGrid\\` for layout; \\`Heading\\`, \\`Text\\` for text; \\`Card\\`, \\`Button\\`, \\`Badge\\`, etc. for all elements. Never use raw HTML (\\`<div>\\`, \\`<h1>\\`, \\`<p>\\`, \\`<button>\\`) when a Chakra UI component exists. Semantic landmarks (\\`<main>\\`, \\`<section>\\`, \\`<nav>\\`, etc.) are the only exception\n- **Pages are thin orchestrators** — compose child components from \\`components/\\`, extract logic into \\`hooks/\\`. Aim for clarity over strict line counts\n- **Use the \\`Path\\` enum** — all route paths come from \\`src/router/paths.ts\\`. Never hardcode path strings\n- **One component per file** — promote complex components to folders with barrel exports (see \\`frontend-modularization\\` skill)\n\n### Environment\n- Check \\`blacksmith.config.json\\` for configured ports and project type\n- Start everything: \\`blacksmith dev\\`\n- API docs (fullstack/backend): \\`http://localhost:<backend-port>/api/docs/\\` (Swagger) or \\`/api/redoc/\\`\n- Python venv: \\`venv/\\` in the backend directory — always use \\`./venv/bin/python\\` or \\`./venv/bin/pip\\`\n\n### Checklist Before Finishing a Task\n1. Backend tests pass (if project has backend): \\`./venv/bin/python manage.py test\\` in the backend directory\n2. Frontend tests pass (if project has frontend): \\`npm test\\` in the frontend directory\n3. Frontend builds (if project has frontend): \\`npm run build\\` in the frontend directory\n4. API types are in sync (fullstack only): \\`blacksmith sync\\`\n5. No lint errors in modified files\n6. All UI uses \\`@chakra-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. No duplicated logic — reusable functions are extracted to \\`utils/\\`\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\n`\n },\n}\n","import net from 'node:net'\nimport concurrently from 'concurrently'\nimport { findProjectRoot, getBackendDir, getFrontendDir, loadConfig, hasBackend, hasFrontend } 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 projectHasBackend = hasBackend(root)\n const projectHasFrontend = hasFrontend(root)\n\n let backendPort: number | undefined\n let frontendPort: number | undefined\n\n try {\n if (projectHasBackend && config.backend) {\n backendPort = await findAvailablePort(config.backend.port)\n if (backendPort !== config.backend.port) {\n log.step(`Backend port ${config.backend.port} in use, using ${backendPort}`)\n }\n }\n if (projectHasFrontend && config.frontend) {\n frontendPort = await findAvailablePort(config.frontend.port)\n if (frontendPort !== config.frontend.port) {\n log.step(`Frontend port ${config.frontend.port} in use, using ${frontendPort}`)\n }\n }\n } catch (err) {\n log.error((err as Error).message)\n process.exit(1)\n }\n\n log.info('Starting development server' + (projectHasBackend && projectHasFrontend ? 's' : '') + '...')\n log.blank()\n if (projectHasBackend && backendPort) {\n log.step(`Django → http://localhost:${backendPort}`)\n log.step(`Swagger → http://localhost:${backendPort}/api/docs/`)\n }\n if (projectHasFrontend && frontendPort) {\n log.step(`Vite → http://localhost:${frontendPort}`)\n }\n if (projectHasBackend && projectHasFrontend) {\n log.step('OpenAPI sync → watching backend .py files')\n }\n log.blank()\n\n const processes: Parameters<typeof concurrently>[0] = []\n\n if (projectHasBackend && backendPort) {\n const backendDir = getBackendDir(root)\n processes.push({\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\n if (projectHasFrontend && frontendPort) {\n const frontendDir = getFrontendDir(root)\n processes.push({\n command: 'npm run dev',\n name: 'vite',\n cwd: frontendDir,\n prefixColor: 'blue',\n })\n }\n\n // Sync watcher only for fullstack projects\n if (projectHasBackend && projectHasFrontend) {\n const backendDir = getBackendDir(root)\n const frontendDir = getFrontendDir(root)\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 processes.push({\n command: `node -e '${watcherCode}'`,\n name: 'sync',\n cwd: frontendDir,\n prefixColor: 'yellow',\n })\n }\n\n const { result } = concurrently(processes, {\n prefix: 'name',\n killOthers: ['failure'],\n restartTries: 3,\n })\n\n const shutdown = () => {\n log.blank()\n log.info('Development server' + (processes.length > 1 ? 's' : '') + ' 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, getProjectType } 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 projectType = getProjectType(root)\n if (projectType !== 'fullstack') {\n log.error('The \"sync\" command is only available for fullstack projects.')\n log.step('It generates frontend TypeScript types from the Django API schema.')\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, hasBackend, hasFrontend } 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 templatesDir = getTemplatesDir()\n const projectHasBackend = hasBackend(root)\n const projectHasFrontend = hasFrontend(root)\n\n const context = { ...names, projectName: name }\n\n // Check if resources already exist\n if (projectHasBackend) {\n const backendDir = getBackendDir(root)\n const backendAppDir = path.join(backendDir, 'apps', names.snakes)\n if (fs.existsSync(backendAppDir)) {\n log.error(`Backend app \"${names.snakes}\" already exists.`)\n process.exit(1)\n }\n }\n\n if (projectHasFrontend) {\n const frontendDir = getFrontendDir(root)\n const frontendPageDir = path.join(frontendDir, 'src', 'pages', names.kebabs)\n if (fs.existsSync(frontendPageDir)) {\n log.error(`Frontend page \"${names.kebabs}\" already exists.`)\n process.exit(1)\n }\n }\n\n // Backend resource generation\n if (projectHasBackend) {\n const backendDir = getBackendDir(root)\n const backendAppDir = path.join(backendDir, 'apps', names.snakes)\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 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\n // 5. Sync OpenAPI (only for fullstack projects)\n if (projectHasBackend && projectHasFrontend) {\n const backendDir = getBackendDir(root)\n const frontendDir = getFrontendDir(root)\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\n // Frontend resource generation\n if (projectHasFrontend) {\n const frontendDir = getFrontendDir(root)\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 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 frontendPageDir = path.join(frontendDir, 'src', 'pages', names.kebabs)\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 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 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 src/router/routes.tsx')\n }\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, hasBackend, hasFrontend } 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 projectHasBackend = hasBackend(root)\n const projectHasFrontend = hasFrontend(root)\n\n // Build frontend\n if (projectHasFrontend) {\n const frontendDir = getFrontendDir(root)\n const frontendSpinner = spinner('Building frontend...')\n try {\n await exec('npm', ['run', 'build'], { cwd: frontendDir, silent: true })\n frontendSpinner.succeed('Frontend built → dist/')\n } catch (error: any) {\n frontendSpinner.fail('Frontend build failed')\n log.error(error.message || error)\n process.exit(1)\n }\n }\n\n // Collect static files\n if (projectHasBackend) {\n const backendDir = getBackendDir(root)\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\n log.blank()\n log.success('Production build complete!')\n log.blank()\n if (projectHasFrontend) log.step('Frontend assets: dist/')\n if (projectHasBackend) log.step('Backend ready for deployment')\n log.blank()\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport { findProjectRoot, loadConfig } 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 config = loadConfig(root)\n const projectType = config.type || 'fullstack'\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\n if (projectType === 'fullstack') {\n log.step('Your project is now a standard Django + React project.')\n } else if (projectType === 'backend') {\n log.step('Your project is now a standard Django project.')\n } else {\n log.step('Your project is now a standard React project.')\n }\n\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\n if (projectType === 'fullstack') {\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 } else if (projectType === 'backend') {\n log.step('Start: ./venv/bin/python manage.py runserver')\n } else {\n log.step('Start: npm run dev')\n }\n\n log.blank()\n}\n","import fs from 'node:fs'\nimport { findProjectRoot, loadConfig, getProjectType } 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 { backendModularizationSkill } from '../skills/backend-modularization.js'\nimport { reactSkill } from '../skills/react.js'\nimport { chakraUiReactSkill } from '../skills/chakra-ui-react.js'\nimport { chakraUiFormsSkill } from '../skills/chakra-ui-forms.js'\nimport { chakraUiAuthSkill } from '../skills/chakra-ui-auth.js'\nimport { blacksmithHooksSkill } from '../skills/blacksmith-hooks.js'\nimport { blacksmithCliSkill } from '../skills/blacksmith-cli.js'\nimport { frontendModularizationSkill } from '../skills/frontend-modularization.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 backendModularizationSkill,\n reactSkill,\n frontendModularizationSkill,\n chakraUiReactSkill,\n chakraUiFormsSkill,\n chakraUiAuthSkill,\n blacksmithHooksSkill,\n blacksmithCliSkill,\n frontendTestingSkill,\n cleanCodeSkill,\n aiGuidelinesSkill,\n]\n\ninterface SetupOptions {\n chakraUiSkill?: 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 includeChakraUiSkill: options.chakraUiSkill !== false,\n projectType: getProjectType(root),\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 fs from 'node:fs'\nimport path from 'node:path'\nimport { findProjectRoot } from '../utils/paths.js'\nimport { log, promptSelect, promptText, promptYesNo } from '../utils/logger.js'\n\ninterface McpPrompt {\n label: string\n defaultValue?: string\n target: 'args' | 'env'\n envVar?: string\n}\n\ninterface McpServerPreset {\n id: string\n name: string\n description: string\n command: string\n args: string[]\n env?: Record<string, string>\n prompts?: McpPrompt[]\n}\n\ninterface McpServerConfig {\n command: string\n args: string[]\n env?: Record<string, string>\n}\n\nconst DONE_OPTION = 'Done — save and exit'\n\nfunction getPresets(projectRoot: string): McpServerPreset[] {\n return [\n {\n id: 'filesystem',\n name: 'Filesystem',\n description: 'Read and write project files',\n command: 'npx',\n args: ['-y', '@modelcontextprotocol/server-filesystem'],\n prompts: [\n {\n label: 'Allowed directory path',\n defaultValue: projectRoot,\n target: 'args',\n },\n ],\n },\n {\n id: 'postgres',\n name: 'PostgreSQL',\n description: 'Query PostgreSQL databases',\n command: 'npx',\n args: ['-y', '@modelcontextprotocol/server-postgres'],\n prompts: [\n {\n label: 'PostgreSQL connection string (e.g. postgresql://user:pass@localhost:5432/dbname)',\n target: 'args',\n },\n ],\n },\n {\n id: 'fetch',\n name: 'Fetch',\n description: 'Make HTTP requests and fetch web content',\n command: 'npx',\n args: ['-y', '@modelcontextprotocol/server-fetch'],\n },\n {\n id: 'github',\n name: 'GitHub',\n description: 'Interact with GitHub repos, issues, and PRs',\n command: 'npx',\n args: ['-y', '@modelcontextprotocol/server-github'],\n prompts: [\n {\n label: 'GitHub personal access token',\n target: 'env',\n envVar: 'GITHUB_PERSONAL_ACCESS_TOKEN',\n },\n ],\n },\n {\n id: 'chakra-ui-docs',\n name: 'Chakra UI Docs',\n description: 'Chakra UI component documentation for AI assistance',\n command: 'npx',\n args: [\n '-y',\n '@anthropic-ai/mcp-docs-server',\n '--url',\n 'https://www.chakra-ui.com/docs',\n '--name',\n 'chakra-ui-docs',\n ],\n },\n {\n id: 'sentry',\n name: 'Sentry',\n description: 'Query errors, view issues, and manage releases in Sentry',\n command: 'npx',\n args: ['-y', '@sentry/mcp-server'],\n prompts: [\n {\n label: 'Sentry auth token',\n target: 'env',\n envVar: 'SENTRY_AUTH_TOKEN',\n },\n {\n label: 'Sentry organization slug',\n target: 'env',\n envVar: 'SENTRY_ORG',\n },\n ],\n },\n {\n id: 'puppeteer',\n name: 'Puppeteer',\n description: 'Browser automation for testing, screenshots, and scraping',\n command: 'npx',\n args: ['-y', '@modelcontextprotocol/server-puppeteer'],\n },\n {\n id: 'memory',\n name: 'Memory',\n description: 'Persistent knowledge graph for cross-session context',\n command: 'npx',\n args: ['-y', '@modelcontextprotocol/server-memory'],\n },\n {\n id: 'slack',\n name: 'Slack',\n description: 'Read/send messages, search channels, and manage threads',\n command: 'npx',\n args: ['-y', '@anthropic-ai/mcp-server-slack'],\n prompts: [\n {\n label: 'Slack bot token (xoxb-...)',\n target: 'env',\n envVar: 'SLACK_BOT_TOKEN',\n },\n {\n label: 'Slack team ID',\n target: 'env',\n envVar: 'SLACK_TEAM_ID',\n },\n ],\n },\n {\n id: 'redis',\n name: 'Redis',\n description: 'Query and manage Redis cache',\n command: 'npx',\n args: ['-y', '@modelcontextprotocol/server-redis'],\n prompts: [\n {\n label: 'Redis URL (e.g. redis://localhost:6379)',\n defaultValue: 'redis://localhost:6379',\n target: 'env',\n envVar: 'REDIS_URL',\n },\n ],\n },\n ]\n}\n\nfunction readSettings(settingsPath: string): Record<string, any> {\n if (!fs.existsSync(settingsPath)) {\n return {}\n }\n\n try {\n return JSON.parse(fs.readFileSync(settingsPath, 'utf-8'))\n } catch {\n return {}\n }\n}\n\nfunction buildServerConfig(preset: McpServerPreset, promptValues: string[]): McpServerConfig {\n const args = [...preset.args]\n const env: Record<string, string> = { ...preset.env }\n\n let valueIndex = 0\n for (const prompt of preset.prompts || []) {\n const value = promptValues[valueIndex++]\n if (prompt.target === 'args') {\n args.push(value)\n } else if (prompt.target === 'env' && prompt.envVar) {\n env[prompt.envVar] = value\n }\n }\n\n const config: McpServerConfig = { command: preset.command, args }\n if (Object.keys(env).length > 0) {\n config.env = env\n }\n return config\n}\n\nexport async function setupMcp() {\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 settingsPath = path.join(root, '.claude', 'settings.local.json')\n const settings = readSettings(settingsPath)\n const existingServers: Record<string, any> = settings.mcpServers || {}\n const newServers: Record<string, McpServerConfig> = {}\n const presets = getPresets(root)\n\n log.blank()\n log.info('Configure MCP servers for Claude Code.')\n log.info('Select servers to add, then choose \"Done\" to save.')\n log.blank()\n\n while (true) {\n const options = presets.map((p) => {\n const configured = existingServers[p.id] || newServers[p.id]\n const suffix = configured ? ' (configured)' : ''\n return `${p.name} — ${p.description}${suffix}`\n })\n options.push(DONE_OPTION)\n\n const choice = await promptSelect('Select an MCP server to configure', options)\n\n if (choice === DONE_OPTION) {\n break\n }\n\n const selectedIndex = options.indexOf(choice)\n const preset = presets[selectedIndex]\n\n if (!preset) break\n\n // Check if already configured\n if (existingServers[preset.id] || newServers[preset.id]) {\n const overwrite = await promptYesNo(\n `${preset.name} is already configured. Overwrite?`,\n false,\n )\n if (!overwrite) continue\n }\n\n // Collect prompt values\n const values: string[] = []\n let skipped = false\n\n for (const prompt of preset.prompts || []) {\n const value = await promptText(prompt.label, prompt.defaultValue)\n if (!value && !prompt.defaultValue) {\n log.warn(`Skipping ${preset.name} — required value not provided.`)\n skipped = true\n break\n }\n values.push(value)\n }\n\n if (skipped) continue\n\n newServers[preset.id] = buildServerConfig(preset, values)\n log.success(`${preset.name} configured.`)\n log.blank()\n }\n\n if (Object.keys(newServers).length === 0) {\n log.info('No new servers configured.')\n return\n }\n\n // Merge and write\n settings.mcpServers = { ...existingServers, ...newServers }\n\n try {\n fs.mkdirSync(path.join(root, '.claude'), { recursive: true })\n fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n', 'utf-8')\n } catch (error: any) {\n log.error(`Failed to write settings: ${error.message}`)\n process.exit(1)\n }\n\n log.blank()\n log.success('MCP servers configured:')\n for (const id of Object.keys(newServers)) {\n const preset = presets.find((p) => p.id === id)\n log.step(` ${preset?.name || id}`)\n }\n log.blank()\n log.info(`Settings written to ${path.relative(root, settingsPath)}`)\n log.blank()\n}\n","import net from 'node:net'\nimport { findProjectRoot, loadConfig } from '../utils/paths.js'\nimport { log } from '../utils/logger.js'\n\nconst DEFAULT_PORT = 3939\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\ninterface StudioOptions {\n port?: string\n}\n\nexport async function studio(options: StudioOptions) {\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 requestedPort = options.port ? parseInt(options.port, 10) : DEFAULT_PORT\n\n let port: number\n try {\n port = await findAvailablePort(requestedPort)\n } catch (err) {\n log.error((err as Error).message)\n process.exit(1)\n }\n\n if (port !== requestedPort) {\n log.step(`Port ${requestedPort} in use, using ${port}`)\n }\n\n log.info(`Starting Blacksmith Studio for \"${config.name}\"...`)\n log.blank()\n\n try {\n const { createStudioServer } = await import('@blacksmith/studio')\n const { server } = await createStudioServer({ projectRoot: root, port })\n\n const url = `http://localhost:${port}`\n log.success('Blacksmith Studio is running!')\n log.blank()\n log.step(`Studio → ${url}`)\n log.step(`Project → ${root}`)\n log.blank()\n log.info('Press Ctrl+C to stop.')\n\n // Open browser\n try {\n const open = (await import('open')).default\n await open(url)\n } catch {\n // Silently ignore if open fails\n }\n\n const shutdown = () => {\n log.blank()\n log.info('Blacksmith Studio stopped.')\n server.close()\n process.exit(0)\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n } catch (error: any) {\n log.error(`Failed to start Studio: ${error.message}`)\n process.exit(1)\n }\n}\n","import { findProjectRoot, getBackendDir, hasBackend } 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 (!hasBackend(root)) {\n log.error('This is a frontend-only project. The \"backend\" command is not available.')\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, hasFrontend } 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 (!hasFrontend(root)) {\n log.error('This is a backend-only project. The \"frontend\" command is not available.')\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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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsgBT;AACF;;;AC/gBA;AAEO,IAAM,wBAA+B;AAAA,EAC1C,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,aAAa;AAAA,EAEb,OAAO,MAA4B;AACjC,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","names":["fs","fs","process","fs","process","Buffer","fs","promisify","childProcess","fs","fsConstants","execFile","powerShellPath","path","promisify","process","execFile","process","promisify","execFile","execFileAsync","promisify","execFile","execFileAsync","promisify","process","execFile","defaultBrowser","execFileAsync","process","process","path","fileURLToPath","childProcess","fs","fsConstants","__dirname","apps","defaultBrowser","powerShellPath","path","fs","path","path","fs","fileURLToPath","__filename","__dirname","path","fs","path","fs","path","fs","path","path","path","fs","path","fs","path","fs","path","fs","fs","path","path","fs","fs","fs","fs","path","fs","path","net","isPortAvailable","net","findAvailablePort","open"]}
|