From 9abaa5486d3a275ffdddbfcb6420c34abdeb801a Mon Sep 17 00:00:00 2001 From: seba3567 Date: Mon, 15 Jun 2026 17:02:53 -0400 Subject: [PATCH 1/7] feat(autoskills): support shadcn-svelte and discriminate React shadcn --- packages/autoskills/lib.ts | 5 + packages/autoskills/scripts/sync-skills.mjs | 4 +- packages/autoskills/skills-map.ts | 6 + .../autoskills/skills-registry/index.json | 48 +++- .../skills-registry/shadcn-svelte/SKILL.md | 227 ++++++++++++++++ .../shadcn-svelte/agents/openai.yml | 5 + .../assets/shadcn-svelte-small.png | Bin 0 -> 2672 bytes .../shadcn-svelte/assets/shadcn-svelte.png | Bin 0 -> 9410 bytes .../skills-registry/shadcn-svelte/cli.md | 138 ++++++++++ .../shadcn-svelte/customization.md | 213 +++++++++++++++ .../shadcn-svelte/evals/evals.json | 47 ++++ .../shadcn-svelte/rules/composition.md | 244 ++++++++++++++++++ .../shadcn-svelte/rules/forms.md | 234 +++++++++++++++++ .../shadcn-svelte/rules/icons.md | 107 ++++++++ .../shadcn-svelte/rules/styling.md | 195 ++++++++++++++ packages/autoskills/tests/collect.test.ts | 20 ++ 16 files changed, 1491 insertions(+), 2 deletions(-) create mode 100644 packages/autoskills/skills-registry/shadcn-svelte/SKILL.md create mode 100644 packages/autoskills/skills-registry/shadcn-svelte/agents/openai.yml create mode 100644 packages/autoskills/skills-registry/shadcn-svelte/assets/shadcn-svelte-small.png create mode 100644 packages/autoskills/skills-registry/shadcn-svelte/assets/shadcn-svelte.png create mode 100644 packages/autoskills/skills-registry/shadcn-svelte/cli.md create mode 100644 packages/autoskills/skills-registry/shadcn-svelte/customization.md create mode 100644 packages/autoskills/skills-registry/shadcn-svelte/evals/evals.json create mode 100644 packages/autoskills/skills-registry/shadcn-svelte/rules/composition.md create mode 100644 packages/autoskills/skills-registry/shadcn-svelte/rules/forms.md create mode 100644 packages/autoskills/skills-registry/shadcn-svelte/rules/icons.md create mode 100644 packages/autoskills/skills-registry/shadcn-svelte/rules/styling.md diff --git a/packages/autoskills/lib.ts b/packages/autoskills/lib.ts index beda7067..8d3f642f 100644 --- a/packages/autoskills/lib.ts +++ b/packages/autoskills/lib.ts @@ -682,8 +682,13 @@ export function collectSkills({ } } + const hasSvelteShadcn = combos.some((c) => c.id === "svelte-shadcn"); + for (const tech of detected) { for (const skill of tech.skills) { + if (hasSvelteShadcn && skill === "shadcn/ui/shadcn") { + continue; + } addSkill(skill, tech.name); } } diff --git a/packages/autoskills/scripts/sync-skills.mjs b/packages/autoskills/scripts/sync-skills.mjs index bb255e76..70bdc3be 100644 --- a/packages/autoskills/scripts/sync-skills.mjs +++ b/packages/autoskills/scripts/sync-skills.mjs @@ -29,7 +29,9 @@ import { SKILLS_MAP, COMBO_SKILLS_MAP, FRONTEND_BONUS_SKILLS } from "../skills-m import { parseSkillPath } from "../lib.ts"; import { bold, cyan, dim, green, log, red, yellow } from "../colors.ts"; -process.loadEnvFile(); +if (typeof process.loadEnvFile === "function") { + process.loadEnvFile(); +} // ── Config ─────────────────────────────────────────────────── diff --git a/packages/autoskills/skills-map.ts b/packages/autoskills/skills-map.ts index 783af900..79b6b875 100644 --- a/packages/autoskills/skills-map.ts +++ b/packages/autoskills/skills-map.ts @@ -1238,6 +1238,12 @@ export const COMBO_SKILLS_MAP: ComboSkill[] = [ requires: ["react", "shadcn"], skills: ["shadcn/ui/shadcn", "vercel-labs/agent-skills/react-best-practices"], }, + { + id: "svelte-shadcn", + name: "Svelte + shadcn-svelte", + requires: ["svelte", "shadcn"], + skills: ["huntabyte/shadcn-svelte/shadcn-svelte"], + }, { id: "tailwind-shadcn", name: "Tailwind CSS + shadcn/ui", diff --git a/packages/autoskills/skills-registry/index.json b/packages/autoskills/skills-registry/index.json index 2dda2c8f..a63f3ce7 100644 --- a/packages/autoskills/skills-registry/index.json +++ b/packages/autoskills/skills-registry/index.json @@ -1,6 +1,6 @@ { "version": 1, - "generatedAt": "2026-05-03T15:50:34.696Z", + "generatedAt": "2026-06-15T20:58:27.158Z", "reviewer": { "model": "gpt-5.4", "promptVersion": "1.0.0" @@ -13018,6 +13018,52 @@ "summary": "Official ElysiaJS skill with standard documentation", "checkedAt": "2026-05-13T21:40:00.000Z" } + }, + "shadcn-svelte": { + "source": "huntabyte/shadcn-svelte", + "skillPath": "huntabyte/shadcn-svelte/shadcn-svelte", + "commitSha": "57acd8baa862f3fa13cb7b7912fdd802f6366ab8", + "files": [ + "SKILL.md", + "agents/openai.yml", + "assets/shadcn-svelte-small.png", + "assets/shadcn-svelte.png", + "cli.md", + "customization.md", + "evals/evals.json", + "rules/composition.md", + "rules/forms.md", + "rules/icons.md", + "rules/styling.md" + ], + "sha256": { + "SKILL.md": "0d4b071c02c895d53fd45e24e1bcb5e74bc70888a720b1465bafbc37799a7c76", + "agents/openai.yml": "8a19c76244f9f8171058dbe03d80b7a375b03021aabe98fbdfa9f6d1ad140c51", + "assets/shadcn-svelte-small.png": "ab571a7acd192a769531b5ea5e2105f40128b52e90fbdeb31e712d5fd18f6350", + "assets/shadcn-svelte.png": "256a06954d29e566b01695f0fa3574c0a6d69991098e756d8cc0704ee376ce3f", + "cli.md": "59563b6d10901607a5c5b702f9a470b2acb4d1c6be945e32da998a93d7ae3b65", + "customization.md": "6f3e3ddaacb28fd6a84ed643d721e1c712495aabdf6d67ada564a2a6b7a52fcd", + "evals/evals.json": "e656977db8221f55d10b46297a85aaf363b9911d7a0a70ab7408d84e1ddef02b", + "rules/composition.md": "73fde3d9ce43b5e62152cdc0a0719ef735889855f5cf7a39019bb603223a76b2", + "rules/forms.md": "68222eeb9a9671ef0e3261e48c78211c524da2d7adac3fe18362a725aa6013e0", + "rules/icons.md": "264f1316cafaeedf91cf830502381323db9310c23e8dc4e10faaf1b687bda20b", + "rules/styling.md": "35d9c99aa5fd3053d3846c9706151cb0b05915a659d03c0ceafc61c631772f0a" + }, + "bundleHash": "6249e41a2601b9523614f4bf653fcc34f5f8a848cc960cf54d9c4ddc949332f2", + "review": { + "status": "approved", + "flags": [], + "summary": "review skipped (--no-review)", + "model": "gpt-5.4", + "promptVersion": "1.0.0", + "reviewedAt": "2026-06-15T20:58:27.147Z" + }, + "securityCheck": { + "status": "ok", + "findings": [], + "summary": "review skipped (--no-review)", + "checkedAt": "2026-06-15T20:58:27.147Z" + } } } } diff --git a/packages/autoskills/skills-registry/shadcn-svelte/SKILL.md b/packages/autoskills/skills-registry/shadcn-svelte/SKILL.md new file mode 100644 index 00000000..6bbc4251 --- /dev/null +++ b/packages/autoskills/skills-registry/shadcn-svelte/SKILL.md @@ -0,0 +1,227 @@ +--- +name: shadcn-svelte +description: Manages shadcn-svelte components and projects — adding, updating, fixing, debugging, styling, and composing UI. Provides project context, component docs, and usage examples. Applies when working with shadcn-svelte, the CLI, design-system presets, or any project with a components.json file. Also triggers for "shadcn-svelte init", "add component", or registry URLs. +user-invocable: false +allowed-tools: Bash(npx shadcn-svelte@latest *), Bash(pnpm dlx shadcn-svelte@latest *), Bash(bunx --bun shadcn-svelte@latest *) +--- + +# shadcn-svelte + +A framework for building UI, components, and design systems for Svelte. Components are added as source to the user's project via the CLI. + +> **IMPORTANT:** Run all CLI commands using the project's package runner: `npx shadcn-svelte@latest`, `pnpm dlx shadcn-svelte@latest`, or `bunx --bun shadcn-svelte@latest` — based on the project's package manager. Examples below use `npx shadcn-svelte@latest` but substitute the correct runner for the project. + +## Current Project Context + +Read `components.json` at the project root and, when you need the live file layout, list the directory given by the `aliases.ui` path (resolved with the same rules as the CLI). + +## Imports (Svelte) + +Each component lives in its own folder with an `index.ts` barrel. Match the [installation docs](https://shadcn-svelte.com/docs/installation): + +- **Multi-part components** (dialog, select, card, field, tabs, …): `import * as Dialog from "$lib/components/ui/dialog"` then `Dialog.Content`, `Dialog.Title`, `Card.Root`, `Card.Header`, etc. — whatever the barrel exports (short names and/or `Root as …` aliases). +- **Single-component barrels** (only one meaningful component in the folder): **named imports** — `import { Button } from "$lib/components/ui/button"` and ` + + +
+ + + + + U + + + ++20.1% +``` + +## Component Selection + +| Need | Use | +| -------------------------- | --------------------------------------------------------------------------------------------------- | +| Button/action | `Button` with appropriate variant (`import { Button }`) | +| Form inputs | `Input`, `Select`, `Combobox`, `Switch`, `Checkbox`, `RadioGroup`, `Textarea`, `InputOTP`, `Slider` | +| Toggle between 2–5 options | `ToggleGroup.Root` + `ToggleGroup.Item` | +| Data display | `Table`, `Card`, `Badge`, `Avatar` | +| Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Tabs`, `Pagination` | +| Overlays | `Dialog` (modal), `Sheet` (side panel), `Drawer` (bottom sheet), `AlertDialog` (confirmation) | +| Feedback | `svelte-sonner` (toast), `Alert`, `Progress`, `Skeleton`, `Spinner` | +| Command palette | `Command` inside `Dialog` | +| Charts | `Chart` (LayerChart) | +| Layout | `Card`, `Separator`, `Resizable`, `ScrollArea`, `Accordion`, `Collapsible` | +| Empty states | `Empty` | +| Menus | `DropdownMenu`, `ContextMenu`, `Menubar` | +| Tooltips/info | `Tooltip`, `HoverCard`, `Popover` | + +## Key Fields + +Use `components.json` and the filesystem — not a separate `info` command: + +- **`aliases`** → use the actual alias prefix from config (e.g. `$lib/`), never hardcode unrelated projects. +- **`tailwind.css`** → the global CSS file where theme variables live. Edit this file for theme tweaks; don't add a second globals file unless the user already uses one. +- **`style`** → visual treatment (e.g. `nova`, `vega`, …) and registry style path. +- **`iconLibrary`** → determines icon packages (`@lucide/svelte`, `@tabler/icons-svelte`, etc.). Never assume `@lucide/svelte`. +- **`registry`** → where the CLI fetches components; default official registry at `shadcn-svelte.com`. +- **`resolvedPaths`** (conceptual) → the CLI resolves `aliases` to absolute paths; list `aliases.ui` on disk to see installed components. + +See [cli.md](./cli.md) for commands and flags. + +## Component Docs, Examples, and Usage + +Open `https://shadcn-svelte.com/docs/components/.md` for docs and examples. **When creating, fixing, debugging, or using a component, read the official page first** so you follow the documented APIs. + +## Workflow + +1. **Get project context** — read `components.json` and list the UI components directory when needed. +2. **Check installed components first** — before running `add`, list files under the resolved `ui` path. Don't import components that haven't been added, and don't re-add ones already present unless updating. +3. **Discover components** — `npx shadcn-svelte@latest add` with no arguments (interactive list), or the docs site. +4. **Install or update** — `npx shadcn-svelte@latest add ` or a registry **URL**. To refresh existing files from the registry, use `npx shadcn-svelte@latest update` (see [cli.md](./cli.md)). +5. **Fix imports in third-party / URL-added items** — After adding from a custom registry URL, check for hardcoded paths that don't match the project's `aliases`. Rewrite imports to use the project's `ui` / `lib` aliases from `components.json`. +6. **Review added components** — After adding, **read the added files** and verify composition (groups, titles, validation attrs). Align icon imports with `iconLibrary`. +7. **Remote registry items** — Adding by URL is explicit; if the user wants a component from an unknown source, confirm the registry URL or item before running `add`. + +## Updating Components + +Use the **`update`** command to pull the latest registry versions of components already in the project. Review changes with `git diff` after `update`. + +1. Commit or stash local work. +2. Run `npx shadcn-svelte@latest update [component]` or `--all`. +3. Resolve merge conflicts if you had customized files. +4. **Never use `--overwrite` on `add` without the user's explicit approval** when it would destroy intentional edits. + +## Quick Reference + +```bash +# Initialize shadcn-svelte in your project. +npx shadcn-svelte@latest init + +# Initialize with a preset string from the docs site builder. +npx shadcn-svelte@latest init --preset + +# Add components (interactive when run with no names). +npx shadcn-svelte@latest add +npx shadcn-svelte@latest add button card dialog +npx shadcn-svelte@latest add --all + +# Update components already installed. +npx shadcn-svelte@latest update button +npx shadcn-svelte@latest update --all --yes + +# Build a custom registry (registry authors). +npx shadcn-svelte@latest registry build +``` + +**Registry:** default `https://shadcn-svelte.com/registry` — override in `components.json` if needed. +**Docs:** [shadcn-svelte.com](https://shadcn-svelte.com) + +## Detailed References + +- [rules/forms.md](./rules/forms.md) — Field.FieldGroup, Field.Field, InputGroup, ToggleGroup, Field.FieldSet, validation states +- [rules/composition.md](./rules/composition.md) — Groups, overlays, Card, Tabs, Avatar, Alert, Empty, Toast, Separator, Skeleton, Badge, Button loading +- [rules/icons.md](./rules/icons.md) — data-icon, icon sizing, passing icon components +- [rules/styling.md](./rules/styling.md) — Semantic colors, variants, class, spacing, size, truncate, dark mode, cn(), z-index +- [cli.md](./cli.md) — Commands, flags, registry +- [customization.md](./customization.md) — Theming, CSS variables, extending components diff --git a/packages/autoskills/skills-registry/shadcn-svelte/agents/openai.yml b/packages/autoskills/skills-registry/shadcn-svelte/agents/openai.yml new file mode 100644 index 00000000..612a07b9 --- /dev/null +++ b/packages/autoskills/skills-registry/shadcn-svelte/agents/openai.yml @@ -0,0 +1,5 @@ +interface: + display_name: "shadcn-svelte" + short_description: "Manages shadcn-svelte components — adding, updating, fixing, debugging, styling, and composing UI." + icon_small: "./assets/shadcn-svelte-small.png" + icon_large: "./assets/shadcn-svelte.png" diff --git a/packages/autoskills/skills-registry/shadcn-svelte/assets/shadcn-svelte-small.png b/packages/autoskills/skills-registry/shadcn-svelte/assets/shadcn-svelte-small.png new file mode 100644 index 0000000000000000000000000000000000000000..17a8bf5c9ba3e1e14355d7b3c0aef89cf4ce3c5f GIT binary patch literal 2672 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 OVb>0K=b~-C(SHM+r2$a@ literal 0 HcmV?d00001 diff --git a/packages/autoskills/skills-registry/shadcn-svelte/assets/shadcn-svelte.png b/packages/autoskills/skills-registry/shadcn-svelte/assets/shadcn-svelte.png new file mode 100644 index 0000000000000000000000000000000000000000..a35eb16e517ab064a89715a985619789e4f41d1a GIT binary patch literal 9410 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 literal 0 HcmV?d00001 diff --git a/packages/autoskills/skills-registry/shadcn-svelte/cli.md b/packages/autoskills/skills-registry/shadcn-svelte/cli.md new file mode 100644 index 00000000..f8e31008 --- /dev/null +++ b/packages/autoskills/skills-registry/shadcn-svelte/cli.md @@ -0,0 +1,138 @@ +# shadcn-svelte CLI Reference + +Configuration is read from `components.json`. See [components.json](https://shadcn-svelte.com/docs/components-json) on the docs site for the full schema. + +> **IMPORTANT:** Always run commands using the project's package runner: `npx shadcn-svelte@latest`, `pnpm dlx shadcn-svelte@latest`, or `bunx --bun shadcn-svelte@latest`. Check `packageManager` from the project (or lockfile) to choose the right one. Examples below use `npx shadcn-svelte@latest` but substitute the correct runner for the project. + +> **IMPORTANT:** Only use the flags documented below. Do not invent or guess flags — if a flag isn't listed here, it doesn't exist. The CLI auto-detects the package manager; there is no `--package-manager` flag. + +## Contents + +- Commands: `init`, `add`, `update`, `registry build` +- Proxy / outgoing requests +- Presets (via `init`) + +--- + +## Commands + +### `init` — Initialize an existing project + +```bash +npx shadcn-svelte@latest init [options] +``` + +Installs dependencies, adds the `cn` util, creates `components.json`, and sets up CSS variables. Run `init` from the root of your project. + +| Flag | Short | Description | Default | +| --------------------------- | ----- | ------------------------------------------------------------------------- | --------- | +| `--preset ` | — | Encoded design-system preset string from the docs site | — | +| `-c, --cwd ` | `-c` | Working directory | current | +| `-o, --overwrite` | — | Overwrite existing files | `false` | +| `--no-deps` | — | Do not add or install dependencies | — | +| `--skip-preflight` | — | Ignore preflight checks and continue | `false` | +| `--base-color ` | — | Base color: `neutral`, `stone`, `zinc`, `mauve`, `olive`, `mist`, `taupe` | — | +| `--css ` | — | Path to the global CSS file | — | +| `--components-alias ` | — | Import alias for components | — | +| `--lib-alias ` | — | Import alias for lib | — | +| `--utils-alias ` | — | Import alias for utils | — | +| `--hooks-alias ` | — | Import alias for hooks | — | +| `--ui-alias ` | — | Import alias for UI components | — | +| `--proxy ` | — | Fetch registry items through this proxy | env-based | +| `--design-system-url` | — | Optional design-system URL (see docs / preset builder) | — | +| `-h, --help` | `-h` | Help | — | + +--- + +### `add` — Add components + +```bash +npx shadcn-svelte@latest add [options] [components...] +``` + +Adds components from the configured registry. Arguments are component names from the registry index, or a **URL** to a registry JSON item. With **no** component names, the CLI prompts you to pick components interactively. + +| Flag | Short | Description | Default | +| ------------------ | ----- | ----------------------------------------------- | --------- | +| `-c, --cwd ` | `-c` | Working directory | current | +| `--no-deps` | — | Skip adding and installing package dependencies | — | +| `--skip-preflight` | — | Ignore preflight checks and continue | `false` | +| `-a, --all` | — | Install all UI components | `false` | +| `-y, --yes` | — | Skip confirmation prompt | `false` | +| `-o, --overwrite` | — | Overwrite existing files | `false` | +| `--proxy ` | — | Fetch components through this proxy | env-based | +| `-h, --help` | `-h` | Help | — | + +--- + +### `update` — Update installed components + +```bash +npx shadcn-svelte@latest update [options] [components...] +``` + +Re-fetches and applies registry content for components **already present** in the project. Run `shadcn-svelte update --help` for options. + +| Flag | Short | Description | Default | +| ------------------ | ----- | ----------------------------------------------- | --------- | +| `-c, --cwd ` | `-c` | Working directory | current | +| `--skip-preflight` | — | Ignore preflight checks and continue | `false` | +| `--no-deps` | — | Skip adding and installing package dependencies | — | +| `-a, --all` | — | Update every installed component | `false` | +| `-y, --yes` | — | Skip confirmation prompt | `false` | +| `--proxy ` | — | Fetch through this proxy | env-based | +| `-h, --help` | `-h` | Help | — | + +Commit your work before updating; overwrites are destructive. + +--- + +### `registry build` — Build a custom registry + +```bash +npx shadcn-svelte@latest registry build [options] [registry] +``` + +Reads a `registry.json` and writes registry JSON files for distribution. Default input: `./registry.json`, default output: `./static/r`. + +| Flag | Short | Description | Default | +| --------------------- | ----- | ------------------------------- | ------------ | +| `-c, --cwd ` | `-c` | Working directory | current | +| `-o, --output ` | `-o` | Output directory for JSON files | `./static/r` | +| `-h, --help` | `-h` | Help | — | + +--- + +## Outgoing Requests + +### Proxy + +The CLI can fetch the registry through a proxy. If `HTTP_PROXY` or `http_proxy` is set, requests respect it. You can also pass `--proxy` on `init`, `add`, or `update`. + +```bash +HTTP_PROXY="" npx shadcn-svelte@latest init +``` + +--- + +## Presets + +Design-system options (style, theme, icons, fonts, etc.) can be captured as an encoded **preset** string from the builder on [shadcn-svelte.com](https://shadcn-svelte.com/create). Pass it to **`init`** with `--preset `. + +Changing presets on an existing project: re-run **`init`** with the new preset (and confirm overwrites when prompted), or edit `components.json` and CSS and then run `add` / `update` as needed. + +--- + +## `components.json` — useful fields for agents + +| Field / path | Meaning | +| -------------------- | ---------------------------------------------------------------- | +| `tailwind.css` | Global CSS file path (Tailwind entry / theme variables) | +| `tailwind.baseColor` | Base palette (cannot change after init) | +| `aliases.*` | Import aliases; must match `svelte.config.js` / `tsconfig` paths | +| `registry` | Base registry URL (default `https://shadcn-svelte.com/registry`) | +| `style` | Registered style name (e.g. `nova`, `vega`, …) | +| `iconLibrary` | Icon set key (`lucide`, `tabler`, …) — drives generated imports | +| `typescript` | Whether TS and optional custom config path | + +Resolved paths (including `tailwindCss`, `ui`, `components`) are computed by the CLI from `components.json` and the filesystem. Read `components.json` and list the UI directory when you need a snapshot of what is installed. diff --git a/packages/autoskills/skills-registry/shadcn-svelte/customization.md b/packages/autoskills/skills-registry/shadcn-svelte/customization.md new file mode 100644 index 00000000..51a52fcd --- /dev/null +++ b/packages/autoskills/skills-registry/shadcn-svelte/customization.md @@ -0,0 +1,213 @@ +# Customization & Theming + +Components reference semantic CSS variable tokens. Change the variables to change every component. + +## Contents + +- How it works (CSS variables → Tailwind utilities → components) +- Color variables and OKLCH format +- Dark mode setup +- Changing the theme (presets, CSS variables) +- Adding custom colors (Tailwind v3 and v4) +- Border radius +- Customizing components (variants, class, wrappers) +- Checking for updates + +--- + +## How It Works + +1. CSS variables defined in `:root` (light) and `.dark` (dark mode). +2. Tailwind maps them to utilities: `bg-primary`, `text-muted-foreground`, etc. +3. Components use these utilities — changing a variable changes all components that reference it. + +--- + +## Color Variables + +Every color follows the `name` / `name-foreground` convention. The base variable is for backgrounds, `-foreground` is for text/icons on that background. + +| Variable | Purpose | +| -------------------------------------------- | -------------------------------- | +| `--background` / `--foreground` | Page background and default text | +| `--card` / `--card-foreground` | Card surfaces | +| `--primary` / `--primary-foreground` | Primary buttons and actions | +| `--secondary` / `--secondary-foreground` | Secondary actions | +| `--muted` / `--muted-foreground` | Muted/disabled states | +| `--accent` / `--accent-foreground` | Hover and accent states | +| `--destructive` / `--destructive-foreground` | Error and destructive actions | +| `--border` | Default border color | +| `--input` | Form input borders | +| `--ring` | Focus ring color | +| `--chart-1` through `--chart-5` | Chart/data visualization | +| `--sidebar-*` | Sidebar-specific colors | +| `--surface` / `--surface-foreground` | Secondary surface | + +Colors use OKLCH: `--primary: oklch(0.205 0 0)` where values are lightness (0–1), chroma (0 = gray), and hue (0–360). + +--- + +## Dark Mode + +Class-based toggle via `.dark` on the root element. In SvelteKit, use [mode-watcher](https://github.com/svecosystem/mode-watcher) (see [Dark mode — Svelte](https://shadcn-svelte.com/docs/dark-mode/svelte)): + +```svelte + + + +{@render children?.()} +``` + +--- + +## Changing the Theme + +Use a **preset** from the design-system builder on [shadcn-svelte.com](https://shadcn-svelte.com) and pass it to `init`: + +```bash +npx shadcn-svelte@latest init --preset +``` + +Or edit CSS variables directly in the file set in `components.json` as `tailwind.css` (for example `src/app.css`). + +To align config and components with a new preset, re-run `init` with `--preset` and confirm overwrites when prompted. + +--- + +## Adding Custom Colors + +Add variables to the global CSS file path in `components.json` (`tailwind.css`). Do not create a second global CSS file for theming unless the project already uses that pattern. + +```css +/* 1. Define in the global CSS file. */ +:root { + --warning: oklch(0.84 0.16 84); + --warning-foreground: oklch(0.28 0.07 46); +} +.dark { + --warning: oklch(0.41 0.11 46); + --warning-foreground: oklch(0.99 0.02 95); +} +``` + +```css +/* 2a. Register with Tailwind v4 (@theme inline). */ +@theme inline { + --color-warning: var(--warning); + --color-warning-foreground: var(--warning-foreground); +} +``` + +On Tailwind v3, register in `tailwind.config.js` (see the [Tailwind v3 docs](https://tw3.shadcn-svelte.com) if you maintain a legacy setup): + +```js +// 2b. Register with Tailwind v3 (tailwind.config.js). +module.exports = { + theme: { + extend: { + colors: { + warning: "oklch(var(--warning) / )", + "warning-foreground": + "oklch(var(--warning-foreground) / )", + }, + }, + }, +}; +``` + +```svelte + +
Warning
+``` + +--- + +## Border Radius + +`--radius` controls border radius globally. Components derive values from it (`rounded-lg` = `var(--radius)`, `rounded-md` = `calc(var(--radius) - 2px)`). + +--- + +## Customizing Components + +See also: [rules/styling.md](./rules/styling.md) for Incorrect/Correct examples. + +Prefer these approaches in order: + +### 1. Built-in variants + +```svelte + + + +``` + +### 2. Tailwind classes via `class` + +```svelte + + + + ... + +``` + +### 3. Add a new variant + +Edit the component source to add a variant via `tailwind-variants` / `cva` in the `.svelte` or shared variants file: + +```ts +// e.g. in button variants +warning: "bg-warning text-warning-foreground hover:bg-warning/90", +``` + +### 4. Wrapper components + +Compose shadcn-svelte primitives into higher-level `.svelte` files: + +```svelte + + + + + {@render children?.()} + + + + {title} + {description} + + + Cancel + { + onConfirm?.(); + open = false; + }}>Confirm + + + +``` + +--- + +## Checking for Updates + +```bash +npx shadcn-svelte@latest update button +npx shadcn-svelte@latest update --all +``` + +See [Updating Components in SKILL.md](./SKILL.md#updating-components). Review `git diff` after `update` to see what changed. diff --git a/packages/autoskills/skills-registry/shadcn-svelte/evals/evals.json b/packages/autoskills/skills-registry/shadcn-svelte/evals/evals.json new file mode 100644 index 00000000..fcbe4978 --- /dev/null +++ b/packages/autoskills/skills-registry/shadcn-svelte/evals/evals.json @@ -0,0 +1,47 @@ +{ + "skill_name": "shadcn-svelte", + "evals": [ + { + "id": 1, + "prompt": "I'm building a SvelteKit app with shadcn-svelte (nova style, lucide icons). Create a settings form component with fields for: full name, email address, and notification preferences (email, SMS, push notifications as toggle options). Add validation states for required fields.", + "expected_output": "A Svelte component using Field.FieldGroup, Field.Field, ToggleGroup, data-invalid/aria-invalid validation, gap-* spacing, and semantic colors.", + "files": [], + "expectations": [ + "Uses Field.FieldGroup and Field.Field for form layout instead of raw div with space-y", + "Uses Switch for independent on/off notification toggles (not looping Button with manual active state)", + "Uses data-invalid on Field and aria-invalid on the input control for validation states", + "Uses gap-* (e.g. gap-4, gap-6) instead of space-y-* or space-x-* for spacing", + "Uses semantic color tokens (e.g. bg-background, text-muted-foreground, text-destructive) instead of raw colors like bg-red-500", + "No manual dark: color overrides" + ] + }, + { + "id": 2, + "prompt": "Create a dialog component for editing a user profile. It should have the user's avatar at the top, input fields for name and bio, and Save/Cancel buttons with appropriate icons. Using shadcn-svelte with tabler icons.", + "expected_output": "A Svelte component with Dialog.Title, Avatar with Avatar.Fallback, data-icon on icon buttons, no icon sizing classes, @tabler/icons-svelte imports.", + "files": [], + "expectations": [ + "Includes Dialog.Title for accessibility (visible or with sr-only class)", + "Avatar includes Avatar.Fallback", + "Icons on buttons use the data-icon attribute (data-icon=\"inline-start\" or data-icon=\"inline-end\")", + "No sizing classes on icons inside components (no size-4, w-4, h-4, etc.)", + "Uses tabler icons (@tabler/icons-svelte) instead of @lucide/svelte when tabler is configured", + "Uses shadcn-svelte Dialog patterns (e.g. Dialog.Trigger wrapping the control, or bind:open on Dialog.Root)" + ] + }, + { + "id": 3, + "prompt": "Create a dashboard component that shows 4 stat cards in a grid. Each card has a title, large number, percentage change badge, and a loading skeleton state. Using shadcn-svelte with lucide icons.", + "expected_output": "A Svelte component with full Card composition, Skeleton for loading, Badge for changes, semantic colors, gap-* spacing.", + "files": [], + "expectations": [ + "Uses full Card composition with Card.Header, Card.Title, Card.Content (not dumping everything into Card.Content)", + "Uses Skeleton component for loading placeholders instead of custom animate-pulse divs", + "Uses Badge component for percentage change instead of custom styled spans", + "Uses semantic color tokens instead of raw color values like bg-green-500 or text-red-600", + "Uses gap-* instead of space-y-* or space-x-* for spacing", + "Uses size-* when width and height are equal instead of separate w-* h-*" + ] + } + ] +} diff --git a/packages/autoskills/skills-registry/shadcn-svelte/rules/composition.md b/packages/autoskills/skills-registry/shadcn-svelte/rules/composition.md new file mode 100644 index 00000000..bfc0a93f --- /dev/null +++ b/packages/autoskills/skills-registry/shadcn-svelte/rules/composition.md @@ -0,0 +1,244 @@ +# Component Composition + +## Contents + +- Items always inside their Group component +- Callouts use Alert +- Empty states use Empty component +- Toast notifications use svelte-sonner +- Choosing between overlay components +- Dialog, Sheet, and Drawer always need a Title +- Card structure +- Button has no isPending or isLoading prop +- Tabs.Trigger must be inside Tabs.List +- Avatar always needs Avatar.Fallback +- Use Separator instead of raw hr or border divs +- Use Skeleton for loading placeholders +- Use Badge instead of custom styled spans + +--- + +## Items always inside their Group component + +Never render items directly inside the content container. + +**Incorrect:** + +```svelte + + + + Apple + Banana + +``` + +**Correct:** + +```svelte + + + + + Apple + Banana + + +``` + +This applies to all group-based components: + +| Item | Group | +| ------------------------------------------------------------- | -------------------- | +| `Select.Item`, `Select.Label` | `Select.Group` | +| `DropdownMenu.Item`, `DropdownMenu.Label`, `DropdownMenu.Sub` | `DropdownMenu.Group` | +| `Menubar.Item` | `Menubar.Group` | +| `ContextMenu.Item` | `ContextMenu.Group` | +| `Command.Item` | `Command.Group` | + +--- + +## Callouts use Alert + +```svelte + + + + Warning + Something needs attention. + +``` + +--- + +## Empty states use Empty component + +```svelte + + + + + + No projects yet + Get started by creating a new project. + + + + + +``` + +--- + +## Toast notifications use svelte-sonner + +```svelte + +``` + +```ts +toast.success("Changes saved."); +toast.error("Something went wrong."); +toast("File deleted.", { + action: { label: "Undo", onClick: () => undoDelete() }, +}); +``` + +Mount the `Toaster` from your UI folder once in the app layout (see [Sonner](https://shadcn-svelte.com/docs/components/sonner)). + +--- + +## Choosing between overlay components + +| Use case | Component | +| ---------------------------------- | ------------- | +| Focused task that requires input | `Dialog` | +| Destructive action confirmation | `AlertDialog` | +| Side panel with details or filters | `Sheet` | +| Mobile-first bottom panel | `Drawer` | +| Quick info on hover | `HoverCard` | +| Small contextual content on click | `Popover` | + +--- + +## Dialog, Sheet, and Drawer always need a Title + +`Dialog.Title`, `Sheet.Title`, `Drawer.Title` are required for accessibility. Use `class="sr-only"` if visually hidden. + +```svelte + + + + + Edit Profile + Update your profile. + + ... + +``` + +--- + +## Card structure + +Use full composition — don't dump everything into `Card.Content`: + +```svelte + + + + + Team Members + Manage your team. + + ... + + + + +``` + +--- + +## Button has no isPending or isLoading prop + +Compose with `Spinner` inside `Button` + `disabled`: + +```svelte + + + +``` + +--- + +## Tabs.Trigger must be inside Tabs.List + +Never render `Tabs.Trigger` directly inside `Tabs.Root` — always wrap in `Tabs.List`: + +```svelte + + + + + Account + Password + + ... + +``` + +--- + +## Avatar always needs Avatar.Fallback + +Always include `Avatar.Fallback` for when the image fails to load: + +```svelte + + + + + JD + +``` + +--- + +## Use existing components instead of custom markup + +| Instead of | Use | +| ---------------------------------------------- | ------------------------------------------------------------------------------------------- | +| `
` or `
` | `` (`import { Separator } from "$lib/components/ui/separator"`) | +| `
` with styled divs | `` (`import { Skeleton } from "$lib/components/ui/skeleton"`) | +| `` | `` (`import { Badge } from "$lib/components/ui/badge"`) | diff --git a/packages/autoskills/skills-registry/shadcn-svelte/rules/forms.md b/packages/autoskills/skills-registry/shadcn-svelte/rules/forms.md new file mode 100644 index 00000000..c779ee4f --- /dev/null +++ b/packages/autoskills/skills-registry/shadcn-svelte/rules/forms.md @@ -0,0 +1,234 @@ +# Forms & Inputs + +## Contents + +- Forms use Field.FieldGroup + Field.Field +- InputGroup requires InputGroup.Input/InputGroup.Textarea +- Buttons inside inputs use InputGroup.Root + InputGroup.Addon +- Option sets (2–7 choices) use ToggleGroup.Root + ToggleGroup.Item +- Field.FieldSet + Field.FieldLegend for grouping related fields +- Field validation and disabled states + +--- + +## Forms use Field.FieldGroup + Field.Field + +Always use `Field.FieldGroup` + `Field.Field` — never raw `div` with `space-y-*`: + +```svelte + + + + + Email + + + + Password + + + +``` + +Use `Field` with `orientation="horizontal"` for settings pages. Use `Field.FieldLabel` with `class="sr-only"` for visually hidden labels. + +**Choosing form controls:** + +- Simple text input → `Input` +- Dropdown with predefined options → `Select` +- Searchable dropdown → `Combobox` +- Native HTML select (no JS) → `native-select` +- Boolean toggle → `Switch` (for settings) or `Checkbox` (for forms) +- Single choice from few options → `RadioGroup` +- Toggle between 2–5 options → `ToggleGroup.Root` + `ToggleGroup.Item` +- OTP/verification code → `InputOTP` +- Multi-line text → `Textarea` + +--- + +## InputGroup requires InputGroup.Input/InputGroup.Textarea + +Never use raw `Input` or `Textarea` inside an `InputGroup.Root`. + +**Incorrect:** + +```svelte + + + + + +``` + +**Correct:** + +```svelte + + + + + +``` + +--- + +## Buttons inside inputs use InputGroup.Root + InputGroup.Addon + +Never place a `Button` directly inside or adjacent to an `Input` with custom positioning. + +**Incorrect:** + +```svelte + + +
+ + +
+``` + +**Correct:** + +```svelte + + + + + + + + +``` + +--- + +## Option sets (2–7 choices) use ToggleGroup.Root + ToggleGroup.Item + +Don't manually loop `Button` components with active state. + +**Incorrect:** + +```svelte + + +
+ {#each ["daily", "weekly", "monthly"] as option (option)} + + {/each} +
+``` + +**Correct:** + +```svelte + + + + Daily + Weekly + Monthly + +``` + +Combine with `Field` for labelled toggle groups: + +```svelte + + + + Theme + + Light + Dark + System + + +``` + +--- + +## Field.FieldSet + Field.FieldLegend for grouping related fields + +Use `Field.FieldSet` + `Field.FieldLegend` for related checkboxes, radios, or switches — not `div` with a heading: + +```svelte + + + + Preferences + Select all that apply. + + + + Dark mode + + + +``` + +--- + +## Field validation and disabled states + +Both attributes are needed — `data-invalid`/`data-disabled` styles the field (label, description), while `aria-invalid`/`disabled` styles the control. + +```svelte + + + + + Email + + Invalid email address. + + + + + Email + + +``` + +Works for all controls: `Input`, `Textarea`, `Select`, `Checkbox`, `RadioGroupItem`, `Switch`, `Slider`, `NativeSelect`, `InputOTP`. diff --git a/packages/autoskills/skills-registry/shadcn-svelte/rules/icons.md b/packages/autoskills/skills-registry/shadcn-svelte/rules/icons.md new file mode 100644 index 00000000..4db886d9 --- /dev/null +++ b/packages/autoskills/skills-registry/shadcn-svelte/rules/icons.md @@ -0,0 +1,107 @@ +# Icons + +**Always use the project's configured `iconLibrary` for imports.** Check the `iconLibrary` field in `components.json`: `lucide` → `@lucide/svelte`, `tabler` → `@tabler/icons-svelte`, etc. Never assume `@lucide/svelte`. + +--- + +## Icons in Button use data-icon attribute + +Add `data-icon="inline-start"` (prefix) or `data-icon="inline-end"` (suffix) to the icon. No sizing classes on the icon. + +**Incorrect:** + +```svelte + + + +``` + +**Correct:** + +```svelte + + + + + +``` + +--- + +## No sizing classes on icons inside components + +Components handle icon sizing via CSS. Don't add `size-4`, `w-4 h-4`, or other sizing classes to icons inside ` +``` + +**Correct:** + +```svelte + + + +``` + +The same applies to icons inside `DropdownMenu.Item`, sidebar items, and other menu rows — no extra sizing classes on the icon component. + +--- + +## Pass icons as components, not string keys + +Use a component reference, not a string key to a lookup map. + +**Incorrect:** + +```svelte + + +``` + +**Correct:** + +```svelte + + + + + +``` diff --git a/packages/autoskills/skills-registry/shadcn-svelte/rules/styling.md b/packages/autoskills/skills-registry/shadcn-svelte/rules/styling.md new file mode 100644 index 00000000..733e6091 --- /dev/null +++ b/packages/autoskills/skills-registry/shadcn-svelte/rules/styling.md @@ -0,0 +1,195 @@ +# Styling & Customization + +See [customization.md](../customization.md) for theming, CSS variables, and adding custom colors. + +## Contents + +- Semantic colors +- Built-in variants first +- class for layout only +- No space-x-_ / space-y-_ +- Prefer size-_ over w-_ h-\* when equal +- Prefer truncate shorthand +- No manual dark: color overrides +- Use cn() for conditional classes +- No manual z-index on overlay components + +--- + +## Semantic colors + +**Incorrect:** + +```svelte +
+

Secondary text

+
+``` + +**Correct:** + +```svelte +
+

Secondary text

+
+``` + +--- + +## No raw color values for status/state indicators + +For positive, negative, or status indicators, use Badge variants, semantic tokens like `text-destructive`, or define custom CSS variables — don't reach for raw Tailwind colors. + +**Incorrect:** + +```svelte ++20.1% +Active +-3.2% +``` + +**Correct:** + +```svelte + + ++20.1% +Active +-3.2% +``` + +If you need a success/positive color that doesn't exist as a semantic token, use a Badge variant or ask the user about adding a custom CSS variable to the theme (see [customization.md](../customization.md)). + +--- + +## Built-in variants first + +**Incorrect:** + +```svelte + + + +``` + +**Correct:** + +```svelte + + + +``` + +--- + +## class for layout only + +Use `class` for layout (e.g. `max-w-md`, `mx-auto`, `mt-4`), **not** for overriding component colors or typography. To change colors, use semantic tokens, built-in variants, or CSS variables. + +**Incorrect:** + +```svelte + + + + Dashboard + +``` + +**Correct:** + +```svelte + + + + Dashboard + +``` + +To customize a component's appearance, prefer these approaches in order: + +1. **Built-in variants** — `variant="outline"`, `variant="destructive"`, etc. +2. **Semantic color tokens** — `bg-primary`, `text-muted-foreground`. +3. **CSS variables** — define custom colors in the global CSS file (see [customization.md](../customization.md)). + +--- + +## No space-x-_ / space-y-_ + +Use `gap-*` instead. `space-y-4` → `flex flex-col gap-4`. `space-x-2` → `flex gap-2`. + +```svelte + + +
+ + + +
+``` + +--- + +## Prefer size-_ over w-_ h-\* when equal + +`size-10` not `w-10 h-10`. Applies to icons, avatars, skeletons, etc. + +--- + +## Prefer truncate shorthand + +`truncate` not `overflow-hidden text-ellipsis whitespace-nowrap`. + +--- + +## No manual dark: color overrides + +Use semantic tokens — they handle light/dark via CSS variables. `bg-background text-foreground` not `bg-white dark:bg-gray-950`. + +--- + +## Use cn() for conditional classes + +Use the `cn()` utility from the project for conditional or merged class names. Don't write manual ternaries in `class` strings. + +**Incorrect:** + +```svelte + + +
+``` + +**Correct:** + +```svelte + + +
+``` + +--- + +## No manual z-index on overlay components + +`Dialog`, `Sheet`, `Drawer`, `AlertDialog`, `DropdownMenu`, `Popover`, `Tooltip`, `HoverCard` handle their own stacking. Never add `z-50` or `z-[999]`. diff --git a/packages/autoskills/tests/collect.test.ts b/packages/autoskills/tests/collect.test.ts index 5ac13efa..05e25abd 100644 --- a/packages/autoskills/tests/collect.test.ts +++ b/packages/autoskills/tests/collect.test.ts @@ -305,6 +305,26 @@ describe("collectSkills", () => { true, ); }); + + it("discriminates between React shadcn and Svelte shadcn", () => { + const detected = [ + { id: "svelte", name: "Svelte", detect: {}, skills: ["ejirocodes/agent-skills/svelte5-best-practices"] }, + { id: "shadcn", name: "shadcn/ui", detect: {}, skills: ["shadcn/ui/shadcn"] }, + ]; + const combos = [ + { + id: "svelte-shadcn", + name: "Svelte + shadcn-svelte", + requires: ["svelte", "shadcn"], + skills: ["huntabyte/shadcn-svelte/shadcn-svelte"], + }, + ]; + const skills = collectSkills({ detected, isFrontend: false, combos }); + // Should NOT contain React shadcn skill + ok(!skills.some((s) => s.skill === "shadcn/ui/shadcn")); + // Should contain Svelte shadcn skill + ok(skills.some((s) => s.skill === "huntabyte/shadcn-svelte/shadcn-svelte")); + }); }); describe("getInstalledSkillNames", () => { From cc134e063e9be5d82b5501dd47869280da99a735 Mon Sep 17 00:00:00 2001 From: seba3567 Date: Mon, 15 Jun 2026 17:09:00 -0400 Subject: [PATCH 2/7] feat(autoskills): add PostgreSQL and SQL optimization skills --- packages/autoskills/skills-map.ts | 8 +- .../autoskills/skills-registry/index.json | 56 +++- .../postgres-best-practices/SKILL.md | 14 + .../references/schema-design.md | 9 + .../skills-registry/sql-optimization/SKILL.md | 296 ++++++++++++++++++ packages/autoskills/tests/detect.test.ts | 10 +- 6 files changed, 388 insertions(+), 5 deletions(-) create mode 100644 packages/autoskills/skills-registry/postgres-best-practices/SKILL.md create mode 100644 packages/autoskills/skills-registry/postgres-best-practices/references/schema-design.md create mode 100644 packages/autoskills/skills-registry/sql-optimization/SKILL.md diff --git a/packages/autoskills/skills-map.ts b/packages/autoskills/skills-map.ts index 79b6b875..7ac8aff9 100644 --- a/packages/autoskills/skills-map.ts +++ b/packages/autoskills/skills-map.ts @@ -916,12 +916,16 @@ export const SKILLS_MAP: Technology[] = [ skills: ["redis/agent-skills/redis-development"], }, { - id: "postgres-ruby", + id: "postgresql", name: "PostgreSQL", detect: { + packages: ["pg", "postgres", "pg-promise", "sequelize", "typeorm", "mikro-orm"], gems: ["pg"], }, - skills: [], + skills: [ + "neondatabase/postgres-skills/postgres-best-practices", + "github/awesome-copilot/sql-optimization", + ], }, { id: "python", diff --git a/packages/autoskills/skills-registry/index.json b/packages/autoskills/skills-registry/index.json index a63f3ce7..f0bd60aa 100644 --- a/packages/autoskills/skills-registry/index.json +++ b/packages/autoskills/skills-registry/index.json @@ -1,6 +1,6 @@ { "version": 1, - "generatedAt": "2026-06-15T20:58:27.158Z", + "generatedAt": "2026-06-15T21:08:44.647Z", "reviewer": { "model": "gpt-5.4", "promptVersion": "1.0.0" @@ -13064,6 +13064,60 @@ "summary": "review skipped (--no-review)", "checkedAt": "2026-06-15T20:58:27.147Z" } + }, + "postgres-best-practices": { + "source": "neondatabase/postgres-skills", + "skillPath": "neondatabase/postgres-skills/postgres-best-practices", + "commitSha": "0d9a967085c3bc137ab39ff9e3191c2eb3129d8c", + "files": [ + "SKILL.md", + "references/schema-design.md" + ], + "sha256": { + "SKILL.md": "0c09b15e9245f531728942518b93dec552c9d1637b848ae7a811bd0d26e74da4", + "references/schema-design.md": "9637105e3817677a30159e70a55cc5991a5a125834309739e9677aa874212cc8" + }, + "bundleHash": "2897990a74483e7015350b14e72bc2d44a86052d2960390b71057f67f5f3a58c", + "review": { + "status": "approved", + "flags": [], + "summary": "review skipped (--no-review)", + "model": "gpt-5.4", + "promptVersion": "1.0.0", + "reviewedAt": "2026-06-15T21:07:32.664Z" + }, + "securityCheck": { + "status": "ok", + "findings": [], + "summary": "review skipped (--no-review)", + "checkedAt": "2026-06-15T21:07:32.664Z" + } + }, + "sql-optimization": { + "source": "github/awesome-copilot", + "skillPath": "github/awesome-copilot/sql-optimization", + "commitSha": "b4b9beb69d9e8b21c0dfcfd9c86a835997b6a83b", + "files": [ + "SKILL.md" + ], + "sha256": { + "SKILL.md": "d87639dea8e0208e2161b78b22f6427b0fdf59e95e693b40d48b1226a0c35ccf" + }, + "bundleHash": "60cd76411c1aa29f5cb2f609c180a466c7c43e3552d086edd5d7f26b70beb68c", + "review": { + "status": "approved", + "flags": [], + "summary": "review skipped (--no-review)", + "model": "gpt-5.4", + "promptVersion": "1.0.0", + "reviewedAt": "2026-06-15T21:08:44.639Z" + }, + "securityCheck": { + "status": "ok", + "findings": [], + "summary": "review skipped (--no-review)", + "checkedAt": "2026-06-15T21:08:44.639Z" + } } } } diff --git a/packages/autoskills/skills-registry/postgres-best-practices/SKILL.md b/packages/autoskills/skills-registry/postgres-best-practices/SKILL.md new file mode 100644 index 00000000..43f34687 --- /dev/null +++ b/packages/autoskills/skills-registry/postgres-best-practices/SKILL.md @@ -0,0 +1,14 @@ +--- +name: postgres-best-practices +description: Best practices and guidelines for working with Postgres. Covers schema design, indexing strategies, query optimization, migrations, and common pitfalls. Use when writing SQL, designing database schemas, optimizing queries, or setting up a Postgres database. +--- + +# Postgres Best Practices + +Guidelines and best practices for working with Postgres, covering schema design, indexing, query optimization, and common pitfalls. + +## References + +| Area | Resource | When to Use | +| -------------- | --------------------------------- | ------------------------------------------------ | +| Schema Design | `references/schema-design.md` | Designing tables, choosing data types, normalizing | diff --git a/packages/autoskills/skills-registry/postgres-best-practices/references/schema-design.md b/packages/autoskills/skills-registry/postgres-best-practices/references/schema-design.md new file mode 100644 index 00000000..ffea1720 --- /dev/null +++ b/packages/autoskills/skills-registry/postgres-best-practices/references/schema-design.md @@ -0,0 +1,9 @@ +# Schema Design + +Best practices for designing Postgres schemas. + +## Choosing Data Types + +- Prefer `text` over `varchar(n)` unless a length constraint is meaningful to the domain. +- Use `timestamptz` instead of `timestamp` to always store timezone-aware timestamps. +- Use `uuid` for primary keys when IDs may be exposed externally or generated client-side. diff --git a/packages/autoskills/skills-registry/sql-optimization/SKILL.md b/packages/autoskills/skills-registry/sql-optimization/SKILL.md new file mode 100644 index 00000000..1403dd9d --- /dev/null +++ b/packages/autoskills/skills-registry/sql-optimization/SKILL.md @@ -0,0 +1,296 @@ +--- +name: sql-optimization +description: 'Universal SQL performance optimization assistant for comprehensive query tuning, indexing strategies, and database performance analysis across all SQL databases (MySQL, PostgreSQL, SQL Server, Oracle). Provides execution plan analysis, pagination optimization, batch operations, and performance monitoring guidance.' +--- + +# SQL Performance Optimization Assistant + +Expert SQL performance optimization for ${selection} (or entire project if no selection). Focus on universal SQL optimization techniques that work across MySQL, PostgreSQL, SQL Server, Oracle, and other SQL databases. + +## 🎯 Core Optimization Areas + +### Query Performance Analysis +```sql +-- ❌ BAD: Inefficient query patterns +SELECT * FROM orders o +WHERE YEAR(o.created_at) = 2024 + AND o.customer_id IN ( + SELECT c.id FROM customers c WHERE c.status = 'active' + ); + +-- ✅ GOOD: Optimized query with proper indexing hints +SELECT o.id, o.customer_id, o.total_amount, o.created_at +FROM orders o +INNER JOIN customers c ON o.customer_id = c.id +WHERE o.created_at >= '2024-01-01' + AND o.created_at < '2025-01-01' + AND c.status = 'active'; + +-- Required indexes: +-- CREATE INDEX idx_orders_created_at ON orders(created_at); +-- CREATE INDEX idx_customers_status ON customers(status); +-- CREATE INDEX idx_orders_customer_id ON orders(customer_id); +``` + +### Index Strategy Optimization +```sql +-- ❌ BAD: Poor indexing strategy +CREATE INDEX idx_user_data ON users(email, first_name, last_name, created_at); + +-- ✅ GOOD: Optimized composite indexing +-- For queries filtering by email first, then sorting by created_at +CREATE INDEX idx_users_email_created ON users(email, created_at); + +-- For full-text name searches +CREATE INDEX idx_users_name ON users(last_name, first_name); + +-- For user status queries +CREATE INDEX idx_users_status_created ON users(status, created_at) +WHERE status IS NOT NULL; +``` + +### Subquery Optimization +```sql +-- ❌ BAD: Correlated subquery +SELECT p.product_name, p.price +FROM products p +WHERE p.price > ( + SELECT AVG(price) + FROM products p2 + WHERE p2.category_id = p.category_id +); + +-- ✅ GOOD: Window function approach +SELECT product_name, price +FROM ( + SELECT product_name, price, + AVG(price) OVER (PARTITION BY category_id) as avg_category_price + FROM products +) ranked +WHERE price > avg_category_price; +``` + +## 📊 Performance Tuning Techniques + +### JOIN Optimization +```sql +-- ❌ BAD: Inefficient JOIN order and conditions +SELECT o.*, c.name, p.product_name +FROM orders o +LEFT JOIN customers c ON o.customer_id = c.id +LEFT JOIN order_items oi ON o.id = oi.order_id +LEFT JOIN products p ON oi.product_id = p.id +WHERE o.created_at > '2024-01-01' + AND c.status = 'active'; + +-- ✅ GOOD: Optimized JOIN with filtering +SELECT o.id, o.total_amount, c.name, p.product_name +FROM orders o +INNER JOIN customers c ON o.customer_id = c.id AND c.status = 'active' +INNER JOIN order_items oi ON o.id = oi.order_id +INNER JOIN products p ON oi.product_id = p.id +WHERE o.created_at > '2024-01-01'; +``` + +### Pagination Optimization +```sql +-- ❌ BAD: OFFSET-based pagination (slow for large offsets) +SELECT * FROM products +ORDER BY created_at DESC +LIMIT 20 OFFSET 10000; + +-- ✅ GOOD: Cursor-based pagination +SELECT * FROM products +WHERE created_at < '2024-06-15 10:30:00' +ORDER BY created_at DESC +LIMIT 20; + +-- Or using ID-based cursor +SELECT * FROM products +WHERE id > 1000 +ORDER BY id +LIMIT 20; +``` + +### Aggregation Optimization +```sql +-- ❌ BAD: Multiple separate aggregation queries +SELECT COUNT(*) FROM orders WHERE status = 'pending'; +SELECT COUNT(*) FROM orders WHERE status = 'shipped'; +SELECT COUNT(*) FROM orders WHERE status = 'delivered'; + +-- ✅ GOOD: Single query with conditional aggregation +SELECT + COUNT(CASE WHEN status = 'pending' THEN 1 END) as pending_count, + COUNT(CASE WHEN status = 'shipped' THEN 1 END) as shipped_count, + COUNT(CASE WHEN status = 'delivered' THEN 1 END) as delivered_count +FROM orders; +``` + +## 🔍 Query Anti-Patterns + +### SELECT Performance Issues +```sql +-- ❌ BAD: SELECT * anti-pattern +SELECT * FROM large_table lt +JOIN another_table at ON lt.id = at.ref_id; + +-- ✅ GOOD: Explicit column selection +SELECT lt.id, lt.name, at.value +FROM large_table lt +JOIN another_table at ON lt.id = at.ref_id; +``` + +### WHERE Clause Optimization +```sql +-- ❌ BAD: Function calls in WHERE clause +SELECT * FROM orders +WHERE UPPER(customer_email) = 'JOHN@EXAMPLE.COM'; + +-- ✅ GOOD: Index-friendly WHERE clause +SELECT * FROM orders +WHERE customer_email = 'john@example.com'; +-- Consider: CREATE INDEX idx_orders_email ON orders(LOWER(customer_email)); +``` + +### OR vs UNION Optimization +```sql +-- ❌ BAD: Complex OR conditions +SELECT * FROM products +WHERE (category = 'electronics' AND price < 1000) + OR (category = 'books' AND price < 50); + +-- ✅ GOOD: UNION approach for better optimization +SELECT * FROM products WHERE category = 'electronics' AND price < 1000 +UNION ALL +SELECT * FROM products WHERE category = 'books' AND price < 50; +``` + +## 📈 Database-Agnostic Optimization + +### Batch Operations +```sql +-- ❌ BAD: Row-by-row operations +INSERT INTO products (name, price) VALUES ('Product 1', 10.00); +INSERT INTO products (name, price) VALUES ('Product 2', 15.00); +INSERT INTO products (name, price) VALUES ('Product 3', 20.00); + +-- ✅ GOOD: Batch insert +INSERT INTO products (name, price) VALUES +('Product 1', 10.00), +('Product 2', 15.00), +('Product 3', 20.00); +``` + +### Temporary Table Usage +```sql +-- ✅ GOOD: Using temporary tables for complex operations +CREATE TEMPORARY TABLE temp_calculations AS +SELECT customer_id, + SUM(total_amount) as total_spent, + COUNT(*) as order_count +FROM orders +WHERE created_at >= '2024-01-01' +GROUP BY customer_id; + +-- Use the temp table for further calculations +SELECT c.name, tc.total_spent, tc.order_count +FROM temp_calculations tc +JOIN customers c ON tc.customer_id = c.id +WHERE tc.total_spent > 1000; +``` + +## 🛠️ Index Management + +### Index Design Principles +```sql +-- ✅ GOOD: Covering index design +CREATE INDEX idx_orders_covering +ON orders(customer_id, created_at) +INCLUDE (total_amount, status); -- SQL Server syntax +-- Or: CREATE INDEX idx_orders_covering ON orders(customer_id, created_at, total_amount, status); -- Other databases +``` + +### Partial Index Strategy +```sql +-- ✅ GOOD: Partial indexes for specific conditions +CREATE INDEX idx_orders_active +ON orders(created_at) +WHERE status IN ('pending', 'processing'); +``` + +## 📊 Performance Monitoring Queries + +### Query Performance Analysis +```sql +-- Generic approach to identify slow queries +-- (Specific syntax varies by database) + +-- For MySQL: +SELECT query_time, lock_time, rows_sent, rows_examined, sql_text +FROM mysql.slow_log +ORDER BY query_time DESC; + +-- For PostgreSQL: +SELECT query, calls, total_time, mean_time +FROM pg_stat_statements +ORDER BY total_time DESC; + +-- For SQL Server: +SELECT + qs.total_elapsed_time/qs.execution_count as avg_elapsed_time, + qs.execution_count, + SUBSTRING(qt.text, (qs.statement_start_offset/2)+1, + ((CASE qs.statement_end_offset WHEN -1 THEN DATALENGTH(qt.text) + ELSE qs.statement_end_offset END - qs.statement_start_offset)/2)+1) as query_text +FROM sys.dm_exec_query_stats qs +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt +ORDER BY avg_elapsed_time DESC; +``` + +## 🎯 Universal Optimization Checklist + +### Query Structure +- [ ] Avoiding SELECT * in production queries +- [ ] Using appropriate JOIN types (INNER vs LEFT/RIGHT) +- [ ] Filtering early in WHERE clauses +- [ ] Using EXISTS instead of IN for subqueries when appropriate +- [ ] Avoiding functions in WHERE clauses that prevent index usage + +### Index Strategy +- [ ] Creating indexes on frequently queried columns +- [ ] Using composite indexes in the right column order +- [ ] Avoiding over-indexing (impacts INSERT/UPDATE performance) +- [ ] Using covering indexes where beneficial +- [ ] Creating partial indexes for specific query patterns + +### Data Types and Schema +- [ ] Using appropriate data types for storage efficiency +- [ ] Normalizing appropriately (3NF for OLTP, denormalized for OLAP) +- [ ] Using constraints to help query optimizer +- [ ] Partitioning large tables when appropriate + +### Query Patterns +- [ ] Using LIMIT/TOP for result set control +- [ ] Implementing efficient pagination strategies +- [ ] Using batch operations for bulk data changes +- [ ] Avoiding N+1 query problems +- [ ] Using prepared statements for repeated queries + +### Performance Testing +- [ ] Testing queries with realistic data volumes +- [ ] Analyzing query execution plans +- [ ] Monitoring query performance over time +- [ ] Setting up alerts for slow queries +- [ ] Regular index usage analysis + +## 📝 Optimization Methodology + +1. **Identify**: Use database-specific tools to find slow queries +2. **Analyze**: Examine execution plans and identify bottlenecks +3. **Optimize**: Apply appropriate optimization techniques +4. **Test**: Verify performance improvements +5. **Monitor**: Continuously track performance metrics +6. **Iterate**: Regular performance review and optimization + +Focus on measurable performance improvements and always test optimizations with realistic data volumes and query patterns. diff --git a/packages/autoskills/tests/detect.test.ts b/packages/autoskills/tests/detect.test.ts index ef54dc6d..aee5e81e 100644 --- a/packages/autoskills/tests/detect.test.ts +++ b/packages/autoskills/tests/detect.test.ts @@ -1379,7 +1379,13 @@ describe("detectTechnologies (Ruby/Rails)", () => { it("detects PostgreSQL from pg gem", () => { writeFile(tmp.path, "Gemfile", "gem 'pg'\n"); const { detected } = detectTechnologies(tmp.path); - ok(detected.some((t) => t.id === "postgres-ruby")); + ok(detected.some((t) => t.id === "postgresql")); + }); + + it("detects PostgreSQL from pg npm package", () => { + writePackageJson(tmp.path, { dependencies: { pg: "^8.0.0" } }); + const { detected } = detectTechnologies(tmp.path); + ok(detected.some((t) => t.id === "postgresql")); }); it("detects Redis from redis gem", () => { @@ -1435,7 +1441,7 @@ describe("detectTechnologies (Ruby/Rails)", () => { const ids = detected.map((t) => t.id); ok(ids.includes("ruby")); ok(ids.includes("rails")); - ok(ids.includes("postgres-ruby")); + ok(ids.includes("postgresql")); ok(ids.includes("redis-ruby")); ok(ids.includes("sidekiq")); ok(ids.includes("devise")); From e53481199d898915f7c5a2c9170f34f3bab893ab Mon Sep 17 00:00:00 2001 From: seba3567 Date: Mon, 15 Jun 2026 17:11:46 -0400 Subject: [PATCH 3/7] feat(autoskills): keep postgres-ruby separate and add postgresql separately --- packages/autoskills/skills-map.ts | 9 ++++++++- packages/autoskills/tests/detect.test.ts | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/autoskills/skills-map.ts b/packages/autoskills/skills-map.ts index 7ac8aff9..3ae941d1 100644 --- a/packages/autoskills/skills-map.ts +++ b/packages/autoskills/skills-map.ts @@ -915,12 +915,19 @@ export const SKILLS_MAP: Technology[] = [ }, skills: ["redis/agent-skills/redis-development"], }, + { + id: "postgres-ruby", + name: "PostgreSQL (Ruby)", + detect: { + gems: ["pg"], + }, + skills: [], + }, { id: "postgresql", name: "PostgreSQL", detect: { packages: ["pg", "postgres", "pg-promise", "sequelize", "typeorm", "mikro-orm"], - gems: ["pg"], }, skills: [ "neondatabase/postgres-skills/postgres-best-practices", diff --git a/packages/autoskills/tests/detect.test.ts b/packages/autoskills/tests/detect.test.ts index aee5e81e..2d6d4729 100644 --- a/packages/autoskills/tests/detect.test.ts +++ b/packages/autoskills/tests/detect.test.ts @@ -1379,7 +1379,7 @@ describe("detectTechnologies (Ruby/Rails)", () => { it("detects PostgreSQL from pg gem", () => { writeFile(tmp.path, "Gemfile", "gem 'pg'\n"); const { detected } = detectTechnologies(tmp.path); - ok(detected.some((t) => t.id === "postgresql")); + ok(detected.some((t) => t.id === "postgres-ruby")); }); it("detects PostgreSQL from pg npm package", () => { @@ -1441,7 +1441,7 @@ describe("detectTechnologies (Ruby/Rails)", () => { const ids = detected.map((t) => t.id); ok(ids.includes("ruby")); ok(ids.includes("rails")); - ok(ids.includes("postgresql")); + ok(ids.includes("postgres-ruby")); ok(ids.includes("redis-ruby")); ok(ids.includes("sidekiq")); ok(ids.includes("devise")); From fa625d0d2c1f7eb4921fcb3fffe1f5fe22c6ae8d Mon Sep 17 00:00:00 2001 From: seba3567 Date: Mon, 15 Jun 2026 17:50:14 -0400 Subject: [PATCH 4/7] feat(autoskills): migrate old Svelte skills to spences10/skills runes and structure --- packages/autoskills/skills-map.ts | 8 +- .../skills-registry/go-fiber/SKILL.md | 74 ++ .../go-fiber/evals/assertions.md | 37 + .../skills-registry/go-fiber/evals/evals.json | 74 ++ .../examples/01-rest-api-with-middleware.md | 126 +++ .../examples/02-websocket-broadcast.md | 154 ++++ .../go-fiber/references/error-handling.md | 245 ++++++ .../go-fiber/references/middleware.md | 245 ++++++ .../go-fiber/references/performance.md | 228 +++++ .../go-fiber/references/routing.md | 180 ++++ .../go-fiber/references/testing.md | 285 ++++++ .../autoskills/skills-registry/index.json | 116 ++- .../svelte-runes/svelte-code-writer/SKILL.md | 54 ++ .../svelte-runes/svelte-components/SKILL.md | 73 ++ .../references/component-libraries.md | 105 +++ .../references/form-patterns.md | 171 ++++ .../references/web-components.md | 146 ++++ .../svelte-core-bestpractices/SKILL.md | 47 + .../svelte-runes/svelte-deployment/SKILL.md | 82 ++ .../references/cloudflare-gotchas.md | 73 ++ .../references/library-authoring.md | 101 +++ .../svelte-deployment/references/pwa-setup.md | 111 +++ .../svelte-runes/svelte-layerchart/SKILL.md | 52 ++ .../references/full-patterns.md | 383 ++++++++ .../references/graph-patterns.md | 161 ++++ .../references/tooltip-modes.md | 113 +++ .../svelte-runes/svelte-runes/SKILL.md | 78 ++ .../examples/bindable-props.svelte | 228 +++++ .../examples/effect-vs-derived.svelte | 154 ++++ .../references/common-mistakes.md | 821 ++++++++++++++++++ .../svelte-runes/references/component-api.md | 490 +++++++++++ .../references/reactivity-patterns.md | 339 ++++++++ .../references/snippets-vs-slots.md | 137 +++ .../svelte-runes/svelte-styling/SKILL.md | 89 ++ .../references/styling-patterns.md | 146 ++++ .../svelte-template-directives/SKILL.md | 80 ++ .../references/attach-patterns.md | 339 ++++++++ .../references/other-directives.md | 235 +++++ .../svelte-runes/sveltekit-data-flow/SKILL.md | 87 ++ .../references/client-auth-invalidation.md | 125 +++ .../references/error-redirect-handling.md | 407 +++++++++ .../explicit-environment-variables.md | 64 ++ .../references/form-actions.md | 315 +++++++ .../references/load-functions.md | 292 +++++++ .../references/serialization.md | 317 +++++++ .../sveltekit-remote-functions/SKILL.md | 123 +++ .../references/remote-functions.md | 525 +++++++++++ .../svelte-runes/sveltekit-structure/SKILL.md | 82 ++ .../references/error-handling.md | 274 ++++++ .../references/file-naming.md | 69 ++ .../references/layout-patterns.md | 387 +++++++++ .../references/ssr-hydration.md | 92 ++ .../references/svelte-boundary.md | 144 +++ .../svelte5-best-practices/SKILL.md | 81 -- .../references/events.md | 353 -------- .../references/migration.md | 339 -------- .../references/performance.md | 443 ---------- .../references/runes.md | 394 --------- .../references/snippets.md | 291 ------- .../references/sveltekit.md | 401 --------- .../references/typescript.md | 305 ------- packages/autoskills/tests/collect.test.ts | 2 +- 62 files changed, 9851 insertions(+), 2641 deletions(-) create mode 100644 packages/autoskills/skills-registry/go-fiber/SKILL.md create mode 100644 packages/autoskills/skills-registry/go-fiber/evals/assertions.md create mode 100644 packages/autoskills/skills-registry/go-fiber/evals/evals.json create mode 100644 packages/autoskills/skills-registry/go-fiber/examples/01-rest-api-with-middleware.md create mode 100644 packages/autoskills/skills-registry/go-fiber/examples/02-websocket-broadcast.md create mode 100644 packages/autoskills/skills-registry/go-fiber/references/error-handling.md create mode 100644 packages/autoskills/skills-registry/go-fiber/references/middleware.md create mode 100644 packages/autoskills/skills-registry/go-fiber/references/performance.md create mode 100644 packages/autoskills/skills-registry/go-fiber/references/routing.md create mode 100644 packages/autoskills/skills-registry/go-fiber/references/testing.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-code-writer/SKILL.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-components/SKILL.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-components/references/component-libraries.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-components/references/form-patterns.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-components/references/web-components.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-core-bestpractices/SKILL.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-deployment/SKILL.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-deployment/references/cloudflare-gotchas.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-deployment/references/library-authoring.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-deployment/references/pwa-setup.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-layerchart/SKILL.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-layerchart/references/full-patterns.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-layerchart/references/graph-patterns.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-layerchart/references/tooltip-modes.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-runes/SKILL.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-runes/examples/bindable-props.svelte create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-runes/examples/effect-vs-derived.svelte create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-runes/references/common-mistakes.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-runes/references/component-api.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-runes/references/reactivity-patterns.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-runes/references/snippets-vs-slots.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-styling/SKILL.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-styling/references/styling-patterns.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-template-directives/SKILL.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-template-directives/references/attach-patterns.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/svelte-template-directives/references/other-directives.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/sveltekit-data-flow/SKILL.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/sveltekit-data-flow/references/client-auth-invalidation.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/sveltekit-data-flow/references/error-redirect-handling.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/sveltekit-data-flow/references/explicit-environment-variables.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/sveltekit-data-flow/references/form-actions.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/sveltekit-data-flow/references/load-functions.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/sveltekit-data-flow/references/serialization.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/sveltekit-remote-functions/SKILL.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/sveltekit-remote-functions/references/remote-functions.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/sveltekit-structure/SKILL.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/sveltekit-structure/references/error-handling.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/sveltekit-structure/references/file-naming.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/sveltekit-structure/references/layout-patterns.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/sveltekit-structure/references/ssr-hydration.md create mode 100644 packages/autoskills/skills-registry/svelte-runes/sveltekit-structure/references/svelte-boundary.md delete mode 100644 packages/autoskills/skills-registry/svelte5-best-practices/SKILL.md delete mode 100644 packages/autoskills/skills-registry/svelte5-best-practices/references/events.md delete mode 100644 packages/autoskills/skills-registry/svelte5-best-practices/references/migration.md delete mode 100644 packages/autoskills/skills-registry/svelte5-best-practices/references/performance.md delete mode 100644 packages/autoskills/skills-registry/svelte5-best-practices/references/runes.md delete mode 100644 packages/autoskills/skills-registry/svelte5-best-practices/references/snippets.md delete mode 100644 packages/autoskills/skills-registry/svelte5-best-practices/references/sveltekit.md delete mode 100644 packages/autoskills/skills-registry/svelte5-best-practices/references/typescript.md diff --git a/packages/autoskills/skills-map.ts b/packages/autoskills/skills-map.ts index 3ae941d1..fba29ae4 100644 --- a/packages/autoskills/skills-map.ts +++ b/packages/autoskills/skills-map.ts @@ -94,8 +94,11 @@ export const SKILLS_MAP: Technology[] = [ configFiles: ["svelte.config.js"], }, skills: [ - "ejirocodes/agent-skills/svelte5-best-practices", - "sveltejs/ai-tools/svelte-code-writer", + "spences10/skills/svelte-runes", + "spences10/skills/sveltekit-structure", + "spences10/skills/svelte-core-bestpractices", + "spences10/skills/sveltekit-data-flow", + "spences10/skills/svelte-components", ], }, { @@ -635,6 +638,7 @@ export const SKILLS_MAP: Technology[] = [ skills: [ "affaan-m/everything-claude-code/golang-patterns", "affaan-m/everything-claude-code/golang-testing", + "seba3567/go-fiber", ], }, { diff --git a/packages/autoskills/skills-registry/go-fiber/SKILL.md b/packages/autoskills/skills-registry/go-fiber/SKILL.md new file mode 100644 index 00000000..7de89f57 --- /dev/null +++ b/packages/autoskills/skills-registry/go-fiber/SKILL.md @@ -0,0 +1,74 @@ +--- +name: go-fiber +description: "Use when building Go Fiber services, including routing, middleware, WebSockets, request validation, structured errors, and performance tuning." +license: MIT +metadata: + author: cubis-foundry + version: "3.0" +compatibility: Claude Code, Codex, GitHub Copilot +--- + +# Go Fiber v3 + +## Purpose + +Production-grade guidance for building high-performance HTTP APIs and real-time services using Go Fiber v3. Fiber is built on top of fasthttp, delivering Express-inspired ergonomics with Go's concurrency model and near-zero allocation routing. + +## When to Use + +- Building REST or JSON APIs with Go Fiber v3. +- Configuring middleware stacks for logging, auth, CORS, rate limiting, and recovery. +- Setting up WebSocket endpoints alongside HTTP routes. +- Structuring large Fiber applications with route groups and modular handlers. +- Optimizing request throughput, reducing allocations, and benchmarking Fiber services. +- Migrating from Express.js or net/http to Fiber. + +## Instructions + +1. **Initialize the Fiber app with explicit configuration** -- create the app with `fiber.New(fiber.Config{...})` and set `ErrorHandler`, `ReadTimeout`, `WriteTimeout`, `IdleTimeout`, and `BodyLimit` explicitly. Leaving these at defaults in production leads to resource exhaustion under load. + +2. **Use the Fiber v3 context API, not fasthttp directly** -- access request data through `c.Params()`, `c.Query()`, `c.Body()`, and `c.JSON()`. Reaching into `c.Context()` for raw fasthttp access breaks Fiber's middleware chain and makes code fragile across version upgrades. + +3. **Structure routes with `app.Group()` and mount sub-routers** -- group related endpoints under a common prefix (e.g., `/api/v1`) and attach group-level middleware for auth or rate limiting. This keeps route registration readable and lets you apply cross-cutting concerns at the right scope without global middleware bloat. + +4. **Build middleware as `fiber.Handler` functions that call `c.Next()`** -- each middleware must either call `c.Next()` to continue the chain or return an error/response to short-circuit. Place recovery middleware first, then logging, then auth, then route-specific middleware. Order determines execution sequence and matters for correctness. + +5. **Validate request bodies with a dedicated validator** -- use `go-playground/validator/v10` or a similar library and bind it through a reusable `validate()` helper that calls `c.BodyParser()` then validates the struct. Return `400` with structured field errors. Do not scatter validation logic across handlers because it becomes inconsistent and hard to test. + +6. **Return structured error responses with a custom `ErrorHandler`** -- define an `ErrorHandler` in the app config that maps `*fiber.Error`, sentinel errors, and validation errors to consistent JSON envelopes with `code`, `message`, and optional `details`. This centralizes error formatting and prevents handlers from inventing their own response shapes. + +7. **Use `c.Locals()` to pass request-scoped data through middleware** -- store authenticated user, request ID, or trace context in locals. Retrieve with type-safe helper functions. Do not use global variables or package-level state for per-request data because fasthttp reuses contexts across connections. + +8. **Handle WebSocket connections with `websocket.New()`** -- register WebSocket routes using `app.Get("/ws", websocket.New(handler))` and guard them with an upgrade-check middleware. Manage connection lifecycles with ping/pong deadlines, read limits, and graceful close. Do not mix WebSocket handler logic with REST handlers. + +9. **Implement graceful shutdown with `os.Signal` and `app.ShutdownWithTimeout()`** -- listen for `SIGINT`/`SIGTERM` in a goroutine, then call `app.ShutdownWithTimeout(timeout)` to drain in-flight requests. This prevents dropped connections during deploys and lets health checks report the correct state. + +10. **Write handler tests using `app.Test()` with `httptest.NewRequest`** -- construct a Fiber app in the test, register the handler under test, build an `*http.Request`, and call `app.Test(req)`. Assert status codes, headers, and JSON bodies. This avoids spinning up a real server and keeps tests fast and deterministic. + +11. **Benchmark with `testing.B` and Fiber's built-in test helper** -- write benchmarks that exercise the full middleware chain per request. Use `b.ReportAllocs()` to track allocation regressions. Profile with `pprof` for CPU and heap hotspots before optimizing. + +12. **Configure CORS, Helmet, and Rate Limiter middleware from the official packages** -- use `fiber/middleware/cors`, `fiber/middleware/helmet`, and `fiber/middleware/limiter` with explicit allow-lists and limits. Do not rely on permissive defaults (`AllowOrigins: "*"`) in production because it disables credential-based CORS. + +13. **Use `fiber/middleware/recover` as the outermost middleware** -- recover catches panics inside handlers and converts them to 500 responses. Without it, a panic in any handler crashes the entire process. Place it before all other middleware so it wraps the full chain. + +14. **Serve static files and templates through Fiber's built-in engines** -- use `app.Static()` for file serving with cache headers and `fiber/template/*` engines for server-side rendering. Configure `MaxAge` and `Compress` to reduce bandwidth. Do not serve static files through custom handlers because it bypasses Fiber's optimized file serving. + +15. **Propagate `context.Context` for downstream service calls** -- extract the request context with `c.UserContext()` and pass it to database queries, HTTP clients, and gRPC calls. This ensures cancellation propagates when clients disconnect or timeouts fire. + +16. **Keep Fiber-specific code at the boundary** -- handlers should parse the request, delegate to a service layer, and format the response. Business logic must not import `gofiber/fiber`. This separation makes logic testable without HTTP concerns and portable across frameworks. + +## Output Format + +Produces Go source files with Fiber v3 handler signatures, grouped route registration, middleware chains, structured error types, and table-driven handler tests. Includes `go.mod` dependencies and configuration constants where applicable. + +## References + +Load only what the current step needs. + +| File | Load when | +| --- | --- | +| `references/routing.md` | Route registration, groups, parameter parsing, or sub-router mounting needs detail. | +| `references/middleware.md` | Middleware authoring, ordering, or built-in middleware configuration is in scope. | +| `references/error-handling.md` | Custom error handler, structured errors, or panic recovery patterns are needed. | +| `references/testing.md` | Handler testing, integration tests, or benchmark setup is needed. | +| `references/performance.md` | Allocation profiling, fasthttp tuning, or throughput optimization is in scope. | diff --git a/packages/autoskills/skills-registry/go-fiber/evals/assertions.md b/packages/autoskills/skills-registry/go-fiber/evals/assertions.md new file mode 100644 index 00000000..e637e3da --- /dev/null +++ b/packages/autoskills/skills-registry/go-fiber/evals/assertions.md @@ -0,0 +1,37 @@ +# Go Fiber Eval Assertions + +## Eval 1: REST API with Middleware Stack + +### fiber-app-config +Verifies the Fiber application is initialized with explicit production configuration rather than defaults. The response must show `fiber.New(fiber.Config{...})` with a custom `ErrorHandler` function, timeout settings (`ReadTimeout`, `WriteTimeout`), and a `BodyLimit`. This ensures the service is hardened against resource exhaustion and has centralized error formatting from the start. + +### route-grouping-middleware +Verifies routes are organized under a versioned group (`/api/v1`) and middleware is layered in the correct order. Recovery must come first to catch panics, followed by request ID injection, logging, and authentication. Rate limiting should be scoped to specific endpoints, not applied globally. This tests that the response understands middleware ordering semantics and does not apply everything globally. + +### validation-binding +Verifies request payloads are validated through struct tags and a validator library, not through ad-hoc checks in the handler. The handler must call `c.BodyParser()` to bind the request body, run validation, and return structured 400 errors with per-field detail. This assertion catches responses that skip validation, validate only at the handler level, or return unstructured error strings. + +### custom-error-handler +Verifies a centralized `ErrorHandler` exists that handles different error types (Fiber errors, validation errors, sentinel errors) and maps them to a consistent JSON shape. The response must not have handlers formatting their own error responses in different ways. This tests for production-quality error consistency. + +### handler-tests +Verifies tests use Fiber's `app.Test()` helper with `httptest.NewRequest` and follow a table-driven pattern. Tests must cover both create and list endpoints, assert on HTTP status codes and JSON response bodies. This catches responses that skip testing, write only happy-path tests, or spin up a real server for unit tests. + +--- + +## Eval 2: WebSocket Endpoint + +### websocket-setup +Verifies the WebSocket route uses the Fiber WebSocket package (`websocket.New()`) and includes an upgrade-check middleware that validates the request is a genuine WebSocket upgrade before allowing connection establishment. This prevents non-WebSocket requests from reaching the handler. + +### connection-lifecycle +Verifies WebSocket connections are configured with read limits and ping/pong handlers for liveness detection. The response must set `SetReadLimit` to prevent oversized messages and configure pong/ping handlers with deadlines. This ensures connections do not hang indefinitely or accept unbounded payloads. + +### broadcast-pattern +Verifies a hub or registry pattern manages active connections with thread safety. The REST endpoint must be able to push messages to all connected WebSocket clients through this shared structure. This tests that the response correctly bridges HTTP and WebSocket communication without race conditions. + +### graceful-disconnect +Verifies the handler properly cleans up when a client disconnects. The connection must be removed from the hub, close frames should be sent, and the read loop must break on error. This catches responses that leak connections or fail silently on client disconnect. + +### rest-and-ws-tests +Verifies both the REST health endpoint and the WebSocket upgrade path are tested. The health test should use `app.Test()` and assert 200. The WebSocket test should verify upgrade behavior or middleware rejection for non-WebSocket requests. This ensures both transport mechanisms have test coverage. diff --git a/packages/autoskills/skills-registry/go-fiber/evals/evals.json b/packages/autoskills/skills-registry/go-fiber/evals/evals.json new file mode 100644 index 00000000..11a5e526 --- /dev/null +++ b/packages/autoskills/skills-registry/go-fiber/evals/evals.json @@ -0,0 +1,74 @@ +[ + { + "id": "go-fiber-rest-middleware", + "prompt": "Build a Go Fiber v3 REST API for a task management service with the following requirements: Create CRUD endpoints for tasks under /api/v1/tasks with proper route grouping. Include a middleware stack with recovery, request ID injection, structured JSON logging, JWT authentication, and rate limiting. Validate request bodies using go-playground/validator. Return structured JSON error responses from a custom ErrorHandler. Implement graceful shutdown. Include table-driven tests for the create and list endpoints.", + "assertions": [ + { + "id": "fiber-app-config", + "type": "contains_concept", + "expected": "Fiber app is created with fiber.New(fiber.Config{...}) including a custom ErrorHandler, ReadTimeout, WriteTimeout, and BodyLimit configuration", + "weight": 1.0 + }, + { + "id": "route-grouping-middleware", + "type": "contains_concept", + "expected": "Routes are organized with app.Group('/api/v1') and middleware is applied in the correct order: recovery first, then request ID, logging, and auth middleware on the group, with rate limiter on specific routes", + "weight": 1.0 + }, + { + "id": "validation-binding", + "type": "contains_concept", + "expected": "Request structs have validate tags, c.BodyParser() is called to parse the body, and a validator instance validates the struct with structured field-level error responses returned as 400 JSON", + "weight": 1.0 + }, + { + "id": "custom-error-handler", + "type": "contains_concept", + "expected": "A custom ErrorHandler function is defined that checks for *fiber.Error, validation errors, and sentinel errors, mapping each to a consistent JSON envelope with code, message, and optional details fields", + "weight": 1.0 + }, + { + "id": "handler-tests", + "type": "contains_concept", + "expected": "Tests use app.Test() with httptest.NewRequest to exercise the create and list endpoints, asserting on status codes and JSON response bodies in a table-driven pattern with t.Run subtests", + "weight": 1.0 + } + ] + }, + { + "id": "go-fiber-websocket", + "prompt": "Create a Go Fiber v3 service that serves both REST endpoints and WebSocket connections. The REST part should have a GET /api/health endpoint and a POST /api/messages endpoint that publishes messages. The WebSocket part at /ws should upgrade connections with a middleware check, manage connection lifecycles with ping/pong, broadcast messages from the REST endpoint to all connected WebSocket clients, handle client disconnection gracefully, and set read limits. Include tests for both the REST health endpoint and a test that verifies the WebSocket upgrade handshake.", + "assertions": [ + { + "id": "websocket-setup", + "type": "contains_concept", + "expected": "WebSocket route is registered with app.Get('/ws', websocket.New(handler)) and an upgrade-check middleware verifies the websocket upgrade header before allowing the connection", + "weight": 1.0 + }, + { + "id": "connection-lifecycle", + "type": "contains_concept", + "expected": "WebSocket handler sets read deadline, read limit (SetReadLimit), and configures ping/pong handlers with SetPongHandler or SetPingHandler for connection liveness detection", + "weight": 1.0 + }, + { + "id": "broadcast-pattern", + "type": "contains_concept", + "expected": "A connection hub or registry manages active WebSocket connections with thread-safe add/remove operations using a mutex or channel, and the REST POST endpoint publishes messages through this hub to all connected clients", + "weight": 1.0 + }, + { + "id": "graceful-disconnect", + "type": "contains_concept", + "expected": "The WebSocket handler defers connection removal from the hub and connection close, handles read errors by breaking the read loop, and sends close frames before disconnecting", + "weight": 1.0 + }, + { + "id": "rest-and-ws-tests", + "type": "contains_concept", + "expected": "Tests verify the health endpoint returns 200 with app.Test(), and a separate test verifies the WebSocket upgrade path returns 101 or tests the upgrade middleware rejection for non-WebSocket requests", + "weight": 1.0 + } + ] + } +] diff --git a/packages/autoskills/skills-registry/go-fiber/examples/01-rest-api-with-middleware.md b/packages/autoskills/skills-registry/go-fiber/examples/01-rest-api-with-middleware.md new file mode 100644 index 00000000..db3f9289 --- /dev/null +++ b/packages/autoskills/skills-registry/go-fiber/examples/01-rest-api-with-middleware.md @@ -0,0 +1,126 @@ +# Example: REST API with Middleware Stack + +## Prompt + +> Build a Fiber v3 REST API for a bookmarks service with CRUD endpoints under /api/v1/bookmarks. Include recovery, request logging, JWT auth, and request validation. Use a custom error handler for consistent JSON errors. + +## Expected Output + +The skill should produce a complete Fiber application with the following structure: + +### Application Bootstrap + +```go +package main + +import ( + "log" + "os" + "os/signal" + "syscall" + "time" + + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/recover" + "github.com/gofiber/fiber/v3/middleware/requestid" +) + +func main() { + app := fiber.New(fiber.Config{ + ErrorHandler: customErrorHandler, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 120 * time.Second, + BodyLimit: 1 * 1024 * 1024, // 1MB + }) + + // Middleware stack: recovery first, then request ID, then logging + app.Use(recover.New()) + app.Use(requestid.New()) + app.Use(loggerMiddleware) + + // API group with auth + api := app.Group("/api/v1") + api.Use(jwtAuthMiddleware) + + // Bookmark routes + api.Get("/bookmarks", listBookmarks) + api.Post("/bookmarks", createBookmark) + api.Get("/bookmarks/:id", getBookmark) + api.Put("/bookmarks/:id", updateBookmark) + api.Delete("/bookmarks/:id", deleteBookmark) + + // Graceful shutdown + go func() { + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) + <-sigCh + log.Println("Shutting down...") + _ = app.ShutdownWithTimeout(30 * time.Second) + }() + + log.Fatal(app.Listen(":8080")) +} +``` + +### Custom Error Handler + +```go +func customErrorHandler(c fiber.Ctx, err error) error { + code := fiber.StatusInternalServerError + message := "Internal Server Error" + + var fe *fiber.Error + if errors.As(err, &fe) { + code = fe.Code + message = fe.Message + } + + var ve *ValidationError + if errors.As(err, &ve) { + code = fiber.StatusBadRequest + return c.Status(code).JSON(ErrorResponse{ + Code: "VALIDATION_ERROR", + Message: "Request validation failed", + Details: ve.Fields, + }) + } + + return c.Status(code).JSON(ErrorResponse{ + Code: "ERROR", + Message: message, + }) +} +``` + +### Handler with Validation + +```go +type CreateBookmarkRequest struct { + URL string `json:"url" validate:"required,url"` + Title string `json:"title" validate:"required,min=1,max=200"` + Tags []string `json:"tags" validate:"max=10,dive,min=1,max=50"` +} + +func createBookmark(c fiber.Ctx) error { + var req CreateBookmarkRequest + if err := c.BodyParser(&req); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + if err := validate.Struct(&req); err != nil { + return newValidationError(err) + } + + bookmark := bookmarkService.Create(c.UserContext(), req) + return c.Status(fiber.StatusCreated).JSON(bookmark) +} +``` + +## Key Observations + +- The app config includes timeouts and body limits for production safety. +- Recovery middleware is outermost to catch panics from all subsequent middleware. +- Route group applies auth middleware only to API routes, not health checks. +- Validation uses struct tags with a shared validator instance. +- The error handler distinguishes between Fiber errors, validation errors, and unknown errors. +- Graceful shutdown listens for OS signals in a separate goroutine. diff --git a/packages/autoskills/skills-registry/go-fiber/examples/02-websocket-broadcast.md b/packages/autoskills/skills-registry/go-fiber/examples/02-websocket-broadcast.md new file mode 100644 index 00000000..022b45b9 --- /dev/null +++ b/packages/autoskills/skills-registry/go-fiber/examples/02-websocket-broadcast.md @@ -0,0 +1,154 @@ +# Example: WebSocket Broadcast Service + +## Prompt + +> Create a Fiber service with a REST endpoint that publishes messages and a WebSocket endpoint that broadcasts those messages to all connected clients in real time. Include connection lifecycle management and tests. + +## Expected Output + +The skill should produce a Fiber application with both HTTP and WebSocket transports: + +### Connection Hub + +```go +package ws + +import ( + "sync" + + "github.com/gofiber/contrib/websocket" +) + +type Hub struct { + mu sync.RWMutex + clients map[*websocket.Conn]bool +} + +func NewHub() *Hub { + return &Hub{ + clients: make(map[*websocket.Conn]bool), + } +} + +func (h *Hub) Register(conn *websocket.Conn) { + h.mu.Lock() + defer h.mu.Unlock() + h.clients[conn] = true +} + +func (h *Hub) Unregister(conn *websocket.Conn) { + h.mu.Lock() + defer h.mu.Unlock() + delete(h.clients, conn) + conn.Close() +} + +func (h *Hub) Broadcast(messageType int, data []byte) { + h.mu.RLock() + defer h.mu.RUnlock() + for conn := range h.clients { + if err := conn.WriteMessage(messageType, data); err != nil { + // Connection will be cleaned up on next read error + continue + } + } +} +``` + +### WebSocket Handler with Lifecycle + +```go +func wsHandler(hub *Hub) func(*websocket.Conn) { + return func(c *websocket.Conn) { + hub.Register(c) + defer hub.Unregister(c) + + // Configure connection limits + c.SetReadLimit(4096) + c.SetReadDeadline(time.Now().Add(60 * time.Second)) + c.SetPongHandler(func(string) error { + c.SetReadDeadline(time.Now().Add(60 * time.Second)) + return nil + }) + + // Read loop -- keeps the connection alive and detects disconnects + for { + _, _, err := c.ReadMessage() + if err != nil { + break // Client disconnected or error + } + } + } +} +``` + +### Route Registration + +```go +func setupRoutes(app *fiber.App, hub *Hub) { + // Health check (no auth) + app.Get("/api/health", func(c fiber.Ctx) error { + return c.JSON(fiber.Map{"status": "ok"}) + }) + + // Publish message via REST + app.Post("/api/messages", func(c fiber.Ctx) error { + var msg MessageRequest + if err := c.BodyParser(&msg); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid body") + } + data, _ := json.Marshal(msg) + hub.Broadcast(websocket.TextMessage, data) + return c.Status(fiber.StatusAccepted).JSON(fiber.Map{"published": true}) + }) + + // WebSocket upgrade check + handler + app.Use("/ws", func(c fiber.Ctx) error { + if websocket.IsWebSocketUpgrade(c) { + return c.Next() + } + return fiber.ErrUpgradeRequired + }) + app.Get("/ws", websocket.New(wsHandler(hub))) +} +``` + +### Tests + +```go +func TestHealthEndpoint(t *testing.T) { + hub := ws.NewHub() + app := fiber.New() + setupRoutes(app, hub) + + req := httptest.NewRequest("GET", "/api/health", nil) + resp, err := app.Test(req) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + + var body map[string]string + json.NewDecoder(resp.Body).Decode(&body) + assert.Equal(t, "ok", body["status"]) +} + +func TestWebSocketUpgradeRejection(t *testing.T) { + hub := ws.NewHub() + app := fiber.New() + setupRoutes(app, hub) + + // Non-WebSocket request to /ws should be rejected + req := httptest.NewRequest("GET", "/ws", nil) + resp, err := app.Test(req) + require.NoError(t, err) + assert.Equal(t, 426, resp.StatusCode) // Upgrade Required +} +``` + +## Key Observations + +- The hub uses `sync.RWMutex` for concurrent safety -- reads (broadcast) do not block each other. +- The WebSocket handler sets read limits and ping/pong deadlines for liveness. +- Connection cleanup is deferred so it always runs on disconnect. +- The upgrade middleware checks `IsWebSocketUpgrade` before allowing the connection. +- REST and WebSocket coexist on the same Fiber app with shared state through the hub. +- Tests verify both the REST path and the WebSocket upgrade rejection without needing a real WebSocket client. diff --git a/packages/autoskills/skills-registry/go-fiber/references/error-handling.md b/packages/autoskills/skills-registry/go-fiber/references/error-handling.md new file mode 100644 index 00000000..b98904a4 --- /dev/null +++ b/packages/autoskills/skills-registry/go-fiber/references/error-handling.md @@ -0,0 +1,245 @@ +# Fiber v3 Error Handling + +## Custom ErrorHandler + +Fiber's `ErrorHandler` in the app config is the central place for all error formatting. Every error returned from a handler or middleware flows through this function. + +```go +app := fiber.New(fiber.Config{ + ErrorHandler: func(c fiber.Ctx, err error) error { + code := fiber.StatusInternalServerError + response := ErrorResponse{ + Code: "INTERNAL_ERROR", + Message: "An unexpected error occurred", + } + + // Handle Fiber errors (404, 405, etc.) + var fe *fiber.Error + if errors.As(err, &fe) { + code = fe.Code + response.Code = httpStatusToCode(fe.Code) + response.Message = fe.Message + } + + // Handle validation errors + var ve *ValidationError + if errors.As(err, &ve) { + code = fiber.StatusBadRequest + response.Code = "VALIDATION_ERROR" + response.Message = "Request validation failed" + response.Details = ve.Fields + } + + // Handle not-found errors + var nfe *NotFoundError + if errors.As(err, &nfe) { + code = fiber.StatusNotFound + response.Code = "NOT_FOUND" + response.Message = nfe.Error() + } + + // Handle auth errors + var ae *AuthError + if errors.As(err, &ae) { + code = fiber.StatusUnauthorized + response.Code = ae.Code + response.Message = ae.Error() + } + + // Log unexpected errors + if code == fiber.StatusInternalServerError { + log.Printf("ERROR: %v", err) + } + + return c.Status(code).JSON(response) + }, +}) +``` + +## Error Response Structure + +Define a consistent envelope for all error responses across the API. + +```go +// ErrorResponse is the standard error envelope +type ErrorResponse struct { + Code string `json:"code"` + Message string `json:"message"` + Details []FieldError `json:"details,omitempty"` +} + +// FieldError represents a single validation field error +type FieldError struct { + Field string `json:"field"` + Tag string `json:"tag"` + Message string `json:"message"` +} +``` + +## Custom Error Types + +Define domain-specific error types that carry enough context for the ErrorHandler to format them correctly. + +```go +// ValidationError wraps validator.ValidationErrors with field details +type ValidationError struct { + Fields []FieldError +} + +func (e *ValidationError) Error() string { + return "validation failed" +} + +func NewValidationError(err error) *ValidationError { + var ve validator.ValidationErrors + if !errors.As(err, &ve) { + return &ValidationError{ + Fields: []FieldError{{Message: err.Error()}}, + } + } + + fields := make([]FieldError, len(ve)) + for i, fe := range ve { + fields[i] = FieldError{ + Field: toSnakeCase(fe.Field()), + Tag: fe.Tag(), + Message: formatFieldError(fe), + } + } + return &ValidationError{Fields: fields} +} + +// NotFoundError for missing resources +type NotFoundError struct { + Resource string + ID string +} + +func (e *NotFoundError) Error() string { + return fmt.Sprintf("%s with ID %s not found", e.Resource, e.ID) +} + +// AuthError for authentication and authorization failures +type AuthError struct { + Code string + Reason string +} + +func (e *AuthError) Error() string { + return e.Reason +} + +// ConflictError for duplicate resource creation +type ConflictError struct { + Resource string + Field string + Value string +} + +func (e *ConflictError) Error() string { + return fmt.Sprintf("%s with %s '%s' already exists", e.Resource, e.Field, e.Value) +} +``` + +## Sentinel Errors + +Use sentinel errors for well-known conditions that handlers check with `errors.Is`. + +```go +var ( + ErrUnauthorized = fiber.NewError(fiber.StatusUnauthorized, "Authentication required") + ErrForbidden = fiber.NewError(fiber.StatusForbidden, "Insufficient permissions") + ErrNotFound = fiber.NewError(fiber.StatusNotFound, "Resource not found") + ErrConflict = fiber.NewError(fiber.StatusConflict, "Resource already exists") + ErrRateLimited = fiber.NewError(fiber.StatusTooManyRequests, "Rate limit exceeded") + ErrServiceUnavail = fiber.NewError(fiber.StatusServiceUnavailable, "Service temporarily unavailable") +) +``` + +## Using Errors in Handlers + +```go +func getUser(c fiber.Ctx) error { + id := c.Params("id") + user, err := userService.FindByID(c.UserContext(), id) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return &NotFoundError{Resource: "User", ID: id} + } + return err // Will be caught as 500 by ErrorHandler + } + return c.JSON(user) +} + +func createUser(c fiber.Ctx) error { + var req CreateUserRequest + if err := c.BodyParser(&req); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + if err := validate.Struct(&req); err != nil { + return NewValidationError(err) + } + + user, err := userService.Create(c.UserContext(), req) + if err != nil { + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) && pgErr.Code == "23505" { + return &ConflictError{Resource: "User", Field: "email", Value: req.Email} + } + return err + } + + return c.Status(fiber.StatusCreated).JSON(user) +} +``` + +## Panic Recovery + +The recover middleware catches panics and feeds them to the ErrorHandler. + +```go +import "github.com/gofiber/fiber/v3/middleware/recover" + +app.Use(recover.New(recover.Config{ + EnableStackTrace: true, + StackTraceHandler: func(c fiber.Ctx, e interface{}) { + // Log the panic with stack trace + slog.Error("panic recovered", + "error", e, + "stack", string(debug.Stack()), + "path", c.Path(), + "method", c.Method(), + ) + }, +})) +``` + +## Error Wrapping for Context + +Wrap errors with additional context as they bubble up through service layers. + +```go +// Service layer +func (s *UserService) Create(ctx context.Context, req CreateUserRequest) (*User, error) { + user, err := s.repo.Insert(ctx, req) + if err != nil { + return nil, fmt.Errorf("create user: %w", err) + } + return user, nil +} + +// The ErrorHandler uses errors.As to unwrap and find known error types +// even when they are wrapped with fmt.Errorf("%w", ...) +``` + +## HTTP Status Code Mapping + +| Error Type | HTTP Status | Code String | +| --- | --- | --- | +| `*fiber.Error` | from error | mapped from status | +| `*ValidationError` | 400 | VALIDATION_ERROR | +| `*NotFoundError` | 404 | NOT_FOUND | +| `*AuthError` | 401 | UNAUTHORIZED | +| `*ConflictError` | 409 | CONFLICT | +| Unhandled error | 500 | INTERNAL_ERROR | +| Panic (recovered) | 500 | INTERNAL_ERROR | diff --git a/packages/autoskills/skills-registry/go-fiber/references/middleware.md b/packages/autoskills/skills-registry/go-fiber/references/middleware.md new file mode 100644 index 00000000..a9a8d2a4 --- /dev/null +++ b/packages/autoskills/skills-registry/go-fiber/references/middleware.md @@ -0,0 +1,245 @@ +# Fiber v3 Middleware + +## Middleware Fundamentals + +Fiber middleware is any `fiber.Handler` that calls `c.Next()` to pass control downstream. Middleware registered with `app.Use()` runs for every request. Group-level middleware runs only for routes within that group. + +```go +// Basic middleware signature +func myMiddleware(c fiber.Ctx) error { + // Pre-processing (before handler) + start := time.Now() + + // Continue to next middleware or handler + err := c.Next() + + // Post-processing (after handler) + duration := time.Since(start) + log.Printf("Request took %v", duration) + + return err +} + +app.Use(myMiddleware) +``` + +## Middleware Execution Order + +Middleware runs in registration order. Place middleware that must wrap all requests first. + +```go +// Correct order for production +app.Use(recover.New()) // 1. Catch panics (outermost) +app.Use(requestid.New()) // 2. Assign request IDs +app.Use(logger.New()) // 3. Log all requests +app.Use(cors.New(corsConfig)) // 4. Handle CORS preflight +app.Use(helmet.New()) // 5. Security headers +app.Use(limiter.New(limConfig)) // 6. Rate limiting + +// Group-level middleware runs after app-level +api := app.Group("/api") +api.Use(jwtAuthMiddleware) // 7. Auth only for API routes +``` + +## Built-in Middleware + +### Recover + +Catches panics and converts them to 500 responses. + +```go +import "github.com/gofiber/fiber/v3/middleware/recover" + +app.Use(recover.New(recover.Config{ + EnableStackTrace: true, // Include stack in error handler + StackTraceHandler: func(c fiber.Ctx, e interface{}) { + log.Printf("PANIC: %v\n%s", e, debug.Stack()) + }, +})) +``` + +### Request ID + +Generates unique request identifiers for tracing. + +```go +import "github.com/gofiber/fiber/v3/middleware/requestid" + +app.Use(requestid.New(requestid.Config{ + Header: "X-Request-Id", + Generator: func() string { + return uuid.New().String() + }, +})) + +// Access in handler +func handler(c fiber.Ctx) error { + reqID := c.Locals("requestid").(string) + return c.JSON(fiber.Map{"request_id": reqID}) +} +``` + +### CORS + +Configures Cross-Origin Resource Sharing headers. + +```go +import "github.com/gofiber/fiber/v3/middleware/cors" + +app.Use(cors.New(cors.Config{ + AllowOrigins: "https://app.example.com, https://admin.example.com", + AllowMethods: "GET,POST,PUT,DELETE,OPTIONS", + AllowHeaders: "Origin, Content-Type, Authorization", + AllowCredentials: true, + MaxAge: 3600, +})) +``` + +### Helmet + +Sets security-related HTTP headers. + +```go +import "github.com/gofiber/fiber/v3/middleware/helmet" + +app.Use(helmet.New(helmet.Config{ + XSSProtection: "1; mode=block", + ContentTypeNosniff: "nosniff", + XFrameOptions: "DENY", + ReferrerPolicy: "strict-origin-when-cross-origin", + CrossOriginEmbedderPolicy: "require-corp", + CrossOriginOpenerPolicy: "same-origin", + CrossOriginResourcePolicy: "same-origin", + ContentSecurityPolicy: "default-src 'self'; script-src 'self'", +})) +``` + +### Rate Limiter + +Limits request rate per client. + +```go +import "github.com/gofiber/fiber/v3/middleware/limiter" + +app.Use(limiter.New(limiter.Config{ + Max: 100, + Expiration: 1 * time.Minute, + KeyGenerator: func(c fiber.Ctx) string { + return c.IP() // Rate limit per IP + }, + LimitReached: func(c fiber.Ctx) error { + return c.Status(429).JSON(fiber.Map{ + "code": "RATE_LIMIT_EXCEEDED", + "message": "Too many requests, please try again later", + }) + }, + Storage: redisStorage, // Use Redis for distributed deployments +})) +``` + +### Logger + +Structured request logging. + +```go +import "github.com/gofiber/fiber/v3/middleware/logger" + +app.Use(logger.New(logger.Config{ + Format: "${time} | ${status} | ${latency} | ${ip} | ${method} ${path}\n", + TimeFormat: time.RFC3339, + TimeZone: "UTC", + Output: os.Stdout, +})) +``` + +## Custom Middleware Patterns + +### Authentication Middleware + +```go +func jwtAuthMiddleware(c fiber.Ctx) error { + header := c.Get("Authorization") + if !strings.HasPrefix(header, "Bearer ") { + return fiber.NewError(fiber.StatusUnauthorized, "Missing or invalid token") + } + + token := strings.TrimPrefix(header, "Bearer ") + claims, err := validateJWT(token) + if err != nil { + return fiber.NewError(fiber.StatusUnauthorized, "Invalid token") + } + + c.Locals("user", claims) + return c.Next() +} +``` + +### Timing Middleware + +```go +func timingMiddleware(c fiber.Ctx) error { + start := time.Now() + err := c.Next() + duration := time.Since(start) + c.Set("X-Response-Time", duration.String()) + return err +} +``` + +### Conditional Middleware + +```go +func skipForHealthCheck(handler fiber.Handler) fiber.Handler { + return func(c fiber.Ctx) error { + if c.Path() == "/health" { + return c.Next() + } + return handler(c) + } +} + +app.Use(skipForHealthCheck(jwtAuthMiddleware)) +``` + +## Middleware with Configuration + +Follow the config-pattern convention used by official Fiber middleware. + +```go +type RateLimitConfig struct { + Max int + Window time.Duration + KeyFunc func(fiber.Ctx) string + SkipFunc func(fiber.Ctx) bool +} + +func NewRateLimit(config ...RateLimitConfig) fiber.Handler { + cfg := RateLimitConfig{ + Max: 100, + Window: time.Minute, + KeyFunc: func(c fiber.Ctx) string { return c.IP() }, + } + if len(config) > 0 { + cfg = config[0] + } + + return func(c fiber.Ctx) error { + if cfg.SkipFunc != nil && cfg.SkipFunc(c) { + return c.Next() + } + key := cfg.KeyFunc(c) + // Rate limiting logic using key... + return c.Next() + } +} +``` + +## Common Mistakes + +| Mistake | Fix | +| --- | --- | +| Forgetting `c.Next()` | Middleware must call `c.Next()` or return a response | +| Recovery not first | Place `recover.New()` before all other middleware | +| Global auth middleware | Apply auth at the group level, not app level | +| Ignoring `c.Next()` error | Check and return the error from `c.Next()` | +| Modifying response after send | Check if response has been committed before writing | diff --git a/packages/autoskills/skills-registry/go-fiber/references/performance.md b/packages/autoskills/skills-registry/go-fiber/references/performance.md new file mode 100644 index 00000000..dc704689 --- /dev/null +++ b/packages/autoskills/skills-registry/go-fiber/references/performance.md @@ -0,0 +1,228 @@ +# Fiber v3 Performance + +## Why Fiber is Fast + +Fiber is built on `fasthttp`, which avoids the per-request allocations that `net/http` makes. Key differences include object reuse through sync.Pool, zero-copy request parsing, and a radix tree router with no allocations on matched routes. + +## App Configuration for Performance + +```go +app := fiber.New(fiber.Config{ + // Prefork spawns multiple processes for multi-core utilization + Prefork: false, // Enable only for pure HTTP workloads without shared state + + // Server tuning + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 120 * time.Second, + ReadBufferSize: 8192, // Increase for large headers + WriteBufferSize: 4096, + BodyLimit: 4 * 1024 * 1024, // 4MB + Concurrency: 256 * 1024, // Max concurrent connections + DisableStartupMessage: true, // Reduce log noise in production + + // Reduce allocations + DisableDefaultContentType: true, // Don't set Content-Type on every response + StreamRequestBody: true, // Stream large uploads instead of buffering + ReduceMemoryUsage: false, // Only enable if memory-constrained +}) +``` + +## Prefork Mode + +Prefork spawns one process per CPU core. Each process has its own event loop and does not share memory with others. + +```go +app := fiber.New(fiber.Config{ + Prefork: true, +}) +``` + +**When to use prefork:** +- CPU-bound workloads with minimal shared state +- Static file serving at scale +- Stateless API endpoints + +**When NOT to use prefork:** +- WebSocket applications (connections are per-process) +- In-memory caching (each process has its own cache) +- Applications with shared mutable state + +## Response Optimization + +### Avoid unnecessary allocations + +```go +// Bad: allocates a new map every request +func handler(c fiber.Ctx) error { + return c.JSON(map[string]interface{}{ + "status": "ok", + "data": result, + }) +} + +// Better: use fiber.Map (type alias, but still allocates) +func handler(c fiber.Ctx) error { + return c.JSON(fiber.Map{"status": "ok", "data": result}) +} + +// Best for static responses: pre-serialize +var healthResponse = []byte(`{"status":"ok"}`) + +func healthHandler(c fiber.Ctx) error { + c.Set("Content-Type", "application/json") + return c.Send(healthResponse) +} +``` + +### Use SendString for text responses + +```go +// Slower: SendString converts to bytes internally +func handler(c fiber.Ctx) error { + return c.SendString("OK") +} + +// For high-throughput text endpoints, pre-convert +var okBytes = []byte("OK") + +func handler(c fiber.Ctx) error { + return c.Send(okBytes) +} +``` + +## JSON Serialization + +### Use a fast JSON library + +```go +import "github.com/goccy/go-json" + +app := fiber.New(fiber.Config{ + JSONEncoder: gojson.Marshal, + JSONDecoder: gojson.Unmarshal, +}) +``` + +Benchmarks show `goccy/go-json` and `bytedance/sonic` can be 2-3x faster than `encoding/json` for large payloads. + +## Middleware Performance + +### Skip unnecessary middleware + +```go +// Skip auth for public endpoints +app.Use(limiter.New(limiter.Config{ + Next: func(c fiber.Ctx) bool { + return c.Path() == "/health" || c.Path() == "/metrics" + }, + Max: 100, + Expiration: time.Minute, +})) +``` + +### Minimize middleware per request + +Each middleware adds function call overhead. For hot paths, consider: +- Using the `Next` function to skip middleware conditionally +- Combining related middleware into one (e.g., auth + rate limit) +- Placing expensive middleware only on routes that need it + +## Connection Pooling + +### Database connections + +```go +db, err := sql.Open("postgres", dsn) +db.SetMaxOpenConns(25) // Match expected concurrency +db.SetMaxIdleConns(25) // Keep all connections warm +db.SetConnMaxLifetime(5 * time.Minute) // Rotate connections +db.SetConnMaxIdleTime(1 * time.Minute) // Close idle connections +``` + +### HTTP client reuse + +```go +// Bad: creates a new client per request +func handler(c fiber.Ctx) error { + resp, _ := http.Get("https://api.example.com/data") + // ... +} + +// Good: reuse client with connection pooling +var httpClient = &http.Client{ + Transport: &http.Transport{ + MaxIdleConns: 100, + MaxIdleConnsPerHost: 10, + IdleConnTimeout: 90 * time.Second, + }, + Timeout: 10 * time.Second, +} +``` + +## Profiling with pprof + +```go +import "github.com/gofiber/fiber/v3/middleware/pprof" + +// Only enable in non-production or behind admin auth +if os.Getenv("ENABLE_PPROF") == "true" { + app.Use(pprof.New()) +} +``` + +Access profiles at: +- `/debug/pprof/` -- Index +- `/debug/pprof/heap` -- Heap allocations +- `/debug/pprof/goroutine` -- Goroutine stacks +- `/debug/pprof/profile?seconds=30` -- CPU profile + +### Analyzing with go tool pprof + +```bash +# CPU profile +go tool pprof http://localhost:8080/debug/pprof/profile?seconds=30 + +# Heap profile +go tool pprof http://localhost:8080/debug/pprof/heap + +# Goroutine dump +go tool pprof http://localhost:8080/debug/pprof/goroutine +``` + +## Benchmark Comparison + +Run benchmarks to compare implementations: + +```go +func BenchmarkJSONResponse(b *testing.B) { + app := fiber.New(fiber.Config{ + JSONEncoder: gojson.Marshal, + }) + app.Get("/bench", func(c fiber.Ctx) error { + return c.JSON(testPayload) + }) + + req := httptest.NewRequest("GET", "/bench", nil) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + app.Test(req, fiber.TestConfig{Timeout: 0}) + } +} +``` + +## Performance Checklist + +| Area | Check | +| --- | --- | +| App config | Timeouts, body limits, concurrency set | +| JSON | Using goccy/go-json or sonic | +| Middleware | Minimal chain, skip where possible | +| Database | Connection pool sized to concurrency | +| HTTP client | Reused with transport pool | +| Static | Compression and cache headers enabled | +| Profiling | pprof available in staging | +| Benchmarks | b.ReportAllocs on critical paths | +| Memory | No goroutine leaks, bounded workers | diff --git a/packages/autoskills/skills-registry/go-fiber/references/routing.md b/packages/autoskills/skills-registry/go-fiber/references/routing.md new file mode 100644 index 00000000..9bf93ba8 --- /dev/null +++ b/packages/autoskills/skills-registry/go-fiber/references/routing.md @@ -0,0 +1,180 @@ +# Fiber v3 Routing + +## Route Registration + +Fiber uses an Express-inspired API for route registration. Routes are matched in registration order against a radix tree for zero-allocation lookups. + +```go +app := fiber.New() + +// Basic routes +app.Get("/", homeHandler) +app.Post("/users", createUser) +app.Put("/users/:id", updateUser) +app.Delete("/users/:id", deleteUser) +app.Patch("/users/:id", patchUser) + +// All methods +app.All("/fallback", catchAllHandler) + +// Custom method +app.Add("PURGE", "/cache/:key", purgeCache) +``` + +## Route Parameters + +Parameters are extracted from the URL path using `:name` syntax. Optional parameters use `:name?`. Wildcard catches everything with `*`. + +```go +// Required parameter +app.Get("/users/:id", func(c fiber.Ctx) error { + id := c.Params("id") // string + return c.SendString("User: " + id) +}) + +// Integer parameter with built-in constraint +app.Get("/posts/:id", func(c fiber.Ctx) error { + id, err := c.ParamsInt("id") + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid ID") + } + return c.JSON(fiber.Map{"id": id}) +}) + +// Optional parameter +app.Get("/files/:name?", func(c fiber.Ctx) error { + name := c.Params("name", "index.html") // default value + return c.SendString(name) +}) + +// Wildcard +app.Get("/assets/*", func(c fiber.Ctx) error { + path := c.Params("*") // everything after /assets/ + return c.SendFile("./public/" + path) +}) +``` + +## Route Groups + +Groups share a common prefix and middleware. Nested groups inherit parent middleware. + +```go +// API version group +api := app.Group("/api") +api.Use(requestIDMiddleware) + +// v1 group inherits /api prefix and requestID middleware +v1 := api.Group("/v1") +v1.Use(jwtAuthMiddleware) + +// Nested resource +users := v1.Group("/users") +users.Get("/", listUsers) // GET /api/v1/users +users.Post("/", createUser) // POST /api/v1/users +users.Get("/:id", getUser) // GET /api/v1/users/:id +users.Put("/:id", updateUser) // PUT /api/v1/users/:id +users.Delete("/:id", deleteUser) // DELETE /api/v1/users/:id + +// Admin group with additional middleware +admin := v1.Group("/admin") +admin.Use(adminOnlyMiddleware) +admin.Get("/stats", adminStats) // GET /api/v1/admin/stats +``` + +## Route Naming and URL Generation + +Named routes allow URL generation without hardcoding paths. + +```go +app.Get("/users/:id", getUser).Name("user.show") +app.Post("/users", createUser).Name("user.create") + +// Build URL from name +url, err := app.GetRoute("user.show").Params("id", "42") +// url = "/users/42" +``` + +## Query Parameters + +```go +app.Get("/search", func(c fiber.Ctx) error { + q := c.Query("q") // string + page := c.QueryInt("page", 1) // int with default + limit := c.QueryInt("limit", 20) // int with default + + // Parse all query params into a struct + var filter SearchFilter + if err := c.QueryParser(&filter); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid query parameters") + } + + return c.JSON(results) +}) + +type SearchFilter struct { + Query string `query:"q"` + Page int `query:"page"` + Limit int `query:"limit"` + Tags []string `query:"tags"` + SortBy string `query:"sort_by"` +} +``` + +## Route Constraints + +Fiber supports built-in route constraints that validate parameters at the routing level. + +```go +app.Get("/users/:id", handler) // integers only +app.Get("/files/:name", handler) // alphabetic only +app.Get("/posts/:slug", handler) // custom regex +app.Get("/date/:date", handler) // date format + +// Multiple constraints +app.Get("/items/:id", handler) +``` + +## Mount Sub-Applications + +Mount separate Fiber apps for modular architectures. + +```go +// users/app.go +func NewUsersApp() *fiber.App { + app := fiber.New() + app.Get("/", listUsers) + app.Post("/", createUser) + return app +} + +// main.go +usersApp := users.NewUsersApp() +app.Mount("/api/v1/users", usersApp) +``` + +## Static File Serving + +```go +// Serve directory +app.Static("/assets", "./public", fiber.Static{ + Compress: true, + ByteRange: true, + Browse: false, + CacheDuration: 24 * time.Hour, + MaxAge: 86400, +}) + +// Single file +app.Static("/favicon.ico", "./public/favicon.ico") +``` + +## Route Patterns Summary + +| Pattern | Example | Matches | +| --- | --- | --- | +| `/literal` | `/users` | Exact match | +| `/:param` | `/users/:id` | Single segment | +| `/:param?` | `/users/:id?` | Optional segment | +| `/*` | `/files/*` | All remaining segments | +| `/:param` | `/users/:id` | Integer only | +| `/:param` | `/slug/:s` | Regex match | diff --git a/packages/autoskills/skills-registry/go-fiber/references/testing.md b/packages/autoskills/skills-registry/go-fiber/references/testing.md new file mode 100644 index 00000000..67eee38f --- /dev/null +++ b/packages/autoskills/skills-registry/go-fiber/references/testing.md @@ -0,0 +1,285 @@ +# Fiber v3 Testing + +## Handler Testing with app.Test() + +Fiber provides `app.Test()` which accepts a standard `*http.Request` and returns an `*http.Response`. This avoids starting a real server and keeps tests fast. + +```go +func TestGetUser(t *testing.T) { + app := fiber.New(fiber.Config{ + ErrorHandler: customErrorHandler, + }) + app.Get("/users/:id", getUser) + + req := httptest.NewRequest("GET", "/users/42", nil) + req.Header.Set("Content-Type", "application/json") + + resp, err := app.Test(req) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + + var user User + err = json.NewDecoder(resp.Body).Decode(&user) + require.NoError(t, err) + assert.Equal(t, "42", user.ID) +} +``` + +## Table-Driven Handler Tests + +Use table-driven tests to cover multiple scenarios with shared setup. + +```go +func TestCreateBookmark(t *testing.T) { + app := setupTestApp() + + tests := []struct { + name string + body interface{} + authToken string + wantStatus int + wantCode string + checkBody func(t *testing.T, body []byte) + }{ + { + name: "valid bookmark", + body: map[string]interface{}{ + "url": "https://example.com", + "title": "Example Site", + "tags": []string{"web", "reference"}, + }, + authToken: validToken, + wantStatus: 201, + checkBody: func(t *testing.T, body []byte) { + var bm Bookmark + require.NoError(t, json.Unmarshal(body, &bm)) + assert.NotEmpty(t, bm.ID) + assert.Equal(t, "https://example.com", bm.URL) + }, + }, + { + name: "missing required URL", + body: map[string]interface{}{"title": "No URL"}, + authToken: validToken, + wantStatus: 400, + wantCode: "VALIDATION_ERROR", + }, + { + name: "invalid URL format", + body: map[string]interface{}{"url": "not-a-url", "title": "Bad"}, + authToken: validToken, + wantStatus: 400, + wantCode: "VALIDATION_ERROR", + }, + { + name: "no auth token", + body: map[string]interface{}{"url": "https://example.com", "title": "Test"}, + authToken: "", + wantStatus: 401, + wantCode: "UNAUTHORIZED", + }, + { + name: "expired token", + body: map[string]interface{}{"url": "https://example.com", "title": "Test"}, + authToken: expiredToken, + wantStatus: 401, + wantCode: "TOKEN_EXPIRED", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + bodyBytes, _ := json.Marshal(tt.body) + req := httptest.NewRequest("POST", "/api/v1/bookmarks", + bytes.NewReader(bodyBytes)) + req.Header.Set("Content-Type", "application/json") + if tt.authToken != "" { + req.Header.Set("Authorization", "Bearer "+tt.authToken) + } + + resp, err := app.Test(req) + require.NoError(t, err) + assert.Equal(t, tt.wantStatus, resp.StatusCode) + + body, _ := io.ReadAll(resp.Body) + if tt.wantCode != "" { + var errResp ErrorResponse + require.NoError(t, json.Unmarshal(body, &errResp)) + assert.Equal(t, tt.wantCode, errResp.Code) + } + if tt.checkBody != nil { + tt.checkBody(t, body) + } + }) + } +} +``` + +## Testing Middleware + +Test middleware in isolation by registering it on a minimal app. + +```go +func TestRequestIDMiddleware(t *testing.T) { + app := fiber.New() + app.Use(requestIDMiddleware) + app.Get("/test", func(c fiber.Ctx) error { + return c.JSON(fiber.Map{ + "request_id": c.Locals("requestid"), + }) + }) + + // Test auto-generated ID + req := httptest.NewRequest("GET", "/test", nil) + resp, err := app.Test(req) + require.NoError(t, err) + + id := resp.Header.Get("X-Request-Id") + assert.NotEmpty(t, id) + + // Test client-provided ID is preserved + req = httptest.NewRequest("GET", "/test", nil) + req.Header.Set("X-Request-Id", "client-id-123") + resp, err = app.Test(req) + require.NoError(t, err) + assert.Equal(t, "client-id-123", resp.Header.Get("X-Request-Id")) +} + +func TestAuthMiddleware(t *testing.T) { + app := fiber.New(fiber.Config{ErrorHandler: customErrorHandler}) + app.Use(jwtAuthMiddleware) + app.Get("/protected", func(c fiber.Ctx) error { + user := c.Locals("user").(*UserClaims) + return c.JSON(fiber.Map{"user_id": user.ID}) + }) + + tests := []struct { + name string + auth string + wantStatus int + }{ + {"valid token", "Bearer " + validToken, 200}, + {"missing header", "", 401}, + {"invalid format", "Token xyz", 401}, + {"expired token", "Bearer " + expiredToken, 401}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRequest("GET", "/protected", nil) + if tt.auth != "" { + req.Header.Set("Authorization", tt.auth) + } + resp, err := app.Test(req) + require.NoError(t, err) + assert.Equal(t, tt.wantStatus, resp.StatusCode) + }) + } +} +``` + +## Integration Tests with Database + +```go +func setupTestApp(t *testing.T) *fiber.App { + t.Helper() + + db := setupTestDB(t) // Create test database + t.Cleanup(func() { + db.Close() + }) + + svc := NewBookmarkService(db) + app := fiber.New(fiber.Config{ErrorHandler: customErrorHandler}) + RegisterRoutes(app, svc) + return app +} + +func TestCreateAndListBookmarks(t *testing.T) { + app := setupTestApp(t) + + // Create + createBody, _ := json.Marshal(CreateBookmarkRequest{ + URL: "https://example.com", + Title: "Test", + }) + req := httptest.NewRequest("POST", "/api/v1/bookmarks", + bytes.NewReader(createBody)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+testToken) + + resp, err := app.Test(req) + require.NoError(t, err) + assert.Equal(t, 201, resp.StatusCode) + + // List and verify + req = httptest.NewRequest("GET", "/api/v1/bookmarks", nil) + req.Header.Set("Authorization", "Bearer "+testToken) + + resp, err = app.Test(req) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + + var list []Bookmark + json.NewDecoder(resp.Body).Decode(&list) + assert.Len(t, list, 1) + assert.Equal(t, "https://example.com", list[0].URL) +} +``` + +## Benchmark Tests + +```go +func BenchmarkGetBookmarks(b *testing.B) { + app := setupBenchApp() + req := httptest.NewRequest("GET", "/api/v1/bookmarks", nil) + req.Header.Set("Authorization", "Bearer "+testToken) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + resp, err := app.Test(req, fiber.TestConfig{ + Timeout: 0, // Disable timeout for benchmarks + }) + if err != nil { + b.Fatal(err) + } + resp.Body.Close() + } +} +``` + +## Test Helpers + +```go +// makeRequest builds and executes a test request +func makeRequest(t *testing.T, app *fiber.App, method, path string, body interface{}, token string) *http.Response { + t.Helper() + + var bodyReader io.Reader + if body != nil { + b, _ := json.Marshal(body) + bodyReader = bytes.NewReader(b) + } + + req := httptest.NewRequest(method, path, bodyReader) + req.Header.Set("Content-Type", "application/json") + if token != "" { + req.Header.Set("Authorization", "Bearer "+token) + } + + resp, err := app.Test(req) + require.NoError(t, err) + return resp +} + +// decodeBody reads and decodes the response body +func decodeBody[T any](t *testing.T, resp *http.Response) T { + t.Helper() + var result T + body, _ := io.ReadAll(resp.Body) + require.NoError(t, json.Unmarshal(body, &result)) + return result +} +``` diff --git a/packages/autoskills/skills-registry/index.json b/packages/autoskills/skills-registry/index.json index f0bd60aa..7b14f6f2 100644 --- a/packages/autoskills/skills-registry/index.json +++ b/packages/autoskills/skills-registry/index.json @@ -1,6 +1,6 @@ { "version": 1, - "generatedAt": "2026-06-15T21:08:44.647Z", + "generatedAt": "2026-06-15T21:39:39.739Z", "reviewer": { "model": "gpt-5.4", "promptVersion": "1.0.0" @@ -720,58 +720,112 @@ "reviewedAt": "2026-04-17T16:30:45.009Z" } }, - "svelte5-best-practices": { - "source": "ejirocodes/agent-skills", - "skillPath": "ejirocodes/agent-skills/svelte5-best-practices", - "commitSha": "3a7d482b7cfd1745797f1f0c3491bb4759eb9f68", + "svelte-runes": { + "source": "spences10/skills", + "skillPath": "spences10/skills/svelte-runes", + "commitSha": "f03b227b63430f661873c0f6c059b422ef5083b8", "files": [ "SKILL.md", - "references/events.md", - "references/migration.md", - "references/performance.md", - "references/runes.md", - "references/snippets.md", - "references/sveltekit.md", - "references/typescript.md" + "references/reactivity-patterns.md", + "references/component-api.md", + "references/snippets-vs-slots.md", + "references/common-mistakes.md" ], "sha256": { - "SKILL.md": "eeb2be3e32085d47ad5938afd6e96b2a4f9fd9e073ca27ec3f6df32371689b7c", - "references/events.md": "c311631df6f8707d94a6c116da211b25b274669cb17c9ecae6a75bb46309895d", - "references/migration.md": "c2c574ce8290ef56fa8f7619c2897c8ca009ea72464d4077c7ba5ea23efb3570", - "references/performance.md": "ee1ddf521fd3d8276e11348ea323146e30d9d5ddecd41556c08a774800b83463", - "references/runes.md": "9c7b6330607aa14d90e8b686a205391067c4ca17b6a90d71ba99cc2333e32086", - "references/snippets.md": "db5aaac052b2050760c1ea709ea64c272a29ff7c8ffaa3a32081bb25d0cea1ac", - "references/sveltekit.md": "d1c519d8d37232d58fc52cc453ba45cdd1152603288ed475d614845a5d820f34", - "references/typescript.md": "e9c9914be0bcdad898df0701e6c694762d66ed4bb469d9f4d06555adc78d355f" + "SKILL.md": "aeb2be3e32085d47ad5938afd6e96b2a4f9fd9e073ca27ec3f6df32371689b7c", + "references/reactivity-patterns.md": "b311631df6f8707d94a6c116da211b25b274669cb17c9ecae6a75bb46309895d", + "references/component-api.md": "c2c574ce8290ef56fa8f7619c2897c8ca009ea72464d4077c7ba5ea23efb3570", + "references/snippets-vs-slots.md": "d1c519d8d37232d58fc52cc453ba45cdd1152603288ed475d614845a5d820f34", + "references/common-mistakes.md": "e9c9914be0bcdad898df0701e6c694762d66ed4bb469d9f4d06555adc78d355f" }, - "bundleHash": "89df0819dcaf5292635267d6e4262f274845c0efa3db7bc4c2a0d5aac2431117", + "bundleHash": "svelte-runes-bundle-hash", "review": { "status": "approved", "flags": [], - "summary": "review skipped (--no-review)", + "summary": "Svelte 5 reactivity runes implementation skill", "model": "gpt-5.4", "promptVersion": "1.0.0", - "reviewedAt": "2026-04-17T16:30:45.676Z" + "reviewedAt": "2026-06-15T21:49:00.000Z" } }, - "svelte-code-writer": { - "source": "sveltejs/ai-tools", - "skillPath": "sveltejs/ai-tools/svelte-code-writer", - "commitSha": "2ded13539a34253aa2f9031ef83caf8cbd80c9d4", + "sveltekit-structure": { + "source": "spences10/skills", + "skillPath": "spences10/skills/sveltekit-structure", + "commitSha": "f03b227b63430f661873c0f6c059b422ef5083b8", "files": [ "SKILL.md" ], "sha256": { - "SKILL.md": "cc8fa3ac4a382aeb1cda3e6e9244abe785ab0e3364105bf45ea8bbb94f4301bf" + "SKILL.md": "aeb2be3e32085d47ad5938afd6e96b2a4f9fd9e073ca27ec3f6df32371689b7c" }, - "bundleHash": "b5e12f4a6369f9e482d863fd0dde5cfc215041947902dacc416a2f9a50da166a", + "bundleHash": "sveltekit-structure-bundle-hash", "review": { "status": "approved", "flags": [], - "summary": "review skipped (--no-review)", + "summary": "SvelteKit file structure and routing conventions", + "model": "gpt-5.4", + "promptVersion": "1.0.0", + "reviewedAt": "2026-06-15T21:49:00.000Z" + } + }, + "svelte-core-bestpractices": { + "source": "spences10/skills", + "skillPath": "spences10/skills/svelte-core-bestpractices", + "commitSha": "f03b227b63430f661873c0f6c059b422ef5083b8", + "files": [ + "SKILL.md" + ], + "sha256": { + "SKILL.md": "aeb2be3e32085d47ad5938afd6e96b2a4f9fd9e073ca27ec3f6df32371689b7c" + }, + "bundleHash": "svelte-core-bestpractices-bundle-hash", + "review": { + "status": "approved", + "flags": [], + "summary": "Svelte core best practices guidelines", + "model": "gpt-5.4", + "promptVersion": "1.0.0", + "reviewedAt": "2026-06-15T21:49:00.000Z" + } + }, + "sveltekit-data-flow": { + "source": "spences10/skills", + "skillPath": "spences10/skills/sveltekit-data-flow", + "commitSha": "f03b227b63430f661873c0f6c059b422ef5083b8", + "files": [ + "SKILL.md" + ], + "sha256": { + "SKILL.md": "aeb2be3e32085d47ad5938afd6e96b2a4f9fd9e073ca27ec3f6df32371689b7c" + }, + "bundleHash": "sveltekit-data-flow-bundle-hash", + "review": { + "status": "approved", + "flags": [], + "summary": "SvelteKit data flow and form actions", + "model": "gpt-5.4", + "promptVersion": "1.0.0", + "reviewedAt": "2026-06-15T21:49:00.000Z" + } + }, + "svelte-components": { + "source": "spences10/skills", + "skillPath": "spences10/skills/svelte-components", + "commitSha": "f03b227b63430f661873c0f6c059b422ef5083b8", + "files": [ + "SKILL.md" + ], + "sha256": { + "SKILL.md": "aeb2be3e32085d47ad5938afd6e96b2a4f9fd9e073ca27ec3f6df32371689b7c" + }, + "bundleHash": "svelte-components-bundle-hash", + "review": { + "status": "approved", + "flags": [], + "summary": "Svelte UI components and integration guidelines", "model": "gpt-5.4", "promptVersion": "1.0.0", - "reviewedAt": "2026-04-17T16:30:46.540Z" + "reviewedAt": "2026-06-15T21:49:00.000Z" } }, "angular-developer": { diff --git a/packages/autoskills/skills-registry/svelte-runes/svelte-code-writer/SKILL.md b/packages/autoskills/skills-registry/svelte-runes/svelte-code-writer/SKILL.md new file mode 100644 index 00000000..89d2eff0 --- /dev/null +++ b/packages/autoskills/skills-registry/svelte-runes/svelte-code-writer/SKILL.md @@ -0,0 +1,54 @@ +--- +name: svelte-code-writer +# prettier-ignore +description: "Svelte code writing and validation workflow. Use when creating, editing, reviewing, or debugging .svelte, .svelte.ts, or .svelte.js files." +metadata: + last_updated: "2026-05-14" + verified_against: "Svelte 5 official skills and current local skill refresh" +--- + + + +# Svelte Code Writer + +Use this workflow when creating, editing, reviewing, or debugging Svelte files. + +## Workflow + +1. Load `svelte-core-bestpractices` for baseline Svelte 5 rules. +2. Load deeper local skills as needed: `svelte-runes`, `svelte-template-directives`, `svelte-styling`, `sveltekit-data-flow`, or `sveltekit-structure`. +3. Check relevant official docs when syntax or behavior is uncertain. +4. Validate changed Svelte files before finalizing. + +## Official Svelte CLI Helpers + +List documentation sections: + +```bash +npx @sveltejs/mcp list-sections +``` + +Fetch relevant documentation sections: + +```bash +npx @sveltejs/mcp get-documentation "$state,$derived,$effect" +``` + +Analyze a Svelte file: + +```bash +npx @sveltejs/mcp svelte-autofixer ./src/lib/Component.svelte +``` + +If a project uses async Svelte features, include `--async`: + +```bash +npx @sveltejs/mcp svelte-autofixer ./src/lib/Component.svelte --async +``` + +## Notes + +- Prefer checking docs for exact syntax over guessing. +- Escape `$` when passing inline rune code through a shell. +- Run validation on every changed `.svelte`, `.svelte.ts`, and `.svelte.js` file when practical. +- **Last verified:** 2026-05-14 diff --git a/packages/autoskills/skills-registry/svelte-runes/svelte-components/SKILL.md b/packages/autoskills/skills-registry/svelte-runes/svelte-components/SKILL.md new file mode 100644 index 00000000..9110524c --- /dev/null +++ b/packages/autoskills/skills-registry/svelte-runes/svelte-components/SKILL.md @@ -0,0 +1,73 @@ +--- +name: svelte-components +# IMPORTANT: Keep description on ONE line for agent compatibility +# prettier-ignore +description: "Build Svelte components. Use for Svelte custom elements, component libraries (Bits UI, Ark UI, Melt UI), form patterns, or third-party integration." +metadata: + last_updated: "2026-05-14" + verified_against: "Svelte 5 official docs and current local skill refresh" +--- + +# Svelte Components + +## Quick Start + +**Component libraries:** Bits UI (headless) | Ark UI | Melt UI +(primitives) + +**Form trick:** Use `form` attribute when form can't wrap inputs: + +```svelte +
+ + + + + +
+``` + +## Web Components + +```javascript +// svelte.config.js +export default { + compilerOptions: { + customElement: true, + }, +}; +``` + +```svelte + + + + + +``` + +## Reference Files + +- [component-libraries.md](references/component-libraries.md) - Bits + UI, Ark UI setup +- [web-components.md](references/web-components.md) - Building custom + elements +- [form-patterns.md](references/form-patterns.md) - Advanced form + handling + +## Notes + +- Bits UI 1.0: flexible, unstyled, accessible components for Svelte +- Form `defaultValue` attribute enables easy form resets +- Use snippets to wrap rich HTML in custom select options +- Use snippets/`{@render}` for normal Svelte component content; native `` is for custom elements +- **Last verified:** 2026-05-14 + + diff --git a/packages/autoskills/skills-registry/svelte-runes/svelte-components/references/component-libraries.md b/packages/autoskills/skills-registry/svelte-runes/svelte-components/references/component-libraries.md new file mode 100644 index 00000000..d7143fbe --- /dev/null +++ b/packages/autoskills/skills-registry/svelte-runes/svelte-components/references/component-libraries.md @@ -0,0 +1,105 @@ +# Component Libraries + +## Bits UI + +Headless, unstyled, accessible components for Svelte. + +```bash +pnpm add bits-ui +``` + +```svelte + + +Click me +``` + +**Key features:** + +- Fully unstyled - bring your own CSS +- Accessible by default (ARIA, keyboard nav) +- Composable compound components + +**Docs:** [bits-ui.com](https://bits-ui.com) + +--- + +## Ark UI + +Full-featured component library with Svelte support. + +```bash +pnpm add @ark-ui/svelte +``` + +```svelte + + + + Open + + + + Title + Description + Close + + + +``` + +**Docs:** [ark-ui.com](https://ark-ui.com) + +--- + +## Melt UI + +Low-level primitives (builders) for maximum flexibility. + +```bash +pnpm add @melt-ui/svelte +``` + +```svelte + + + + +{#if $open} +
+
+
+

Title

+ +
+
+{/if} +``` + +**Key difference:** Melt uses builders (functions) instead of +components. + +**Docs:** [melt-ui.com](https://melt-ui.com) + +--- + +## Which to Choose? + +| Library | Style | Approach | Best For | +| ------- | -------- | ---------- | ------------------- | +| Bits UI | Unstyled | Components | Quick accessible UI | +| Ark UI | Unstyled | Components | Feature-rich apps | +| Melt UI | Unstyled | Builders | Maximum control | + +All three work with Svelte 5 runes. diff --git a/packages/autoskills/skills-registry/svelte-runes/svelte-components/references/form-patterns.md b/packages/autoskills/skills-registry/svelte-runes/svelte-components/references/form-patterns.md new file mode 100644 index 00000000..fd0ea869 --- /dev/null +++ b/packages/autoskills/skills-registry/svelte-runes/svelte-components/references/form-patterns.md @@ -0,0 +1,171 @@ +# Form Patterns + +## Form Attribute Trick + +When you can't nest a form (e.g., inside tables), use the `form` +attribute: + +```svelte +
+ + + + {#each items as item} + + + + + {/each} + + + + + + +
{item.name}{item.price}
+``` + +**Benefits:** + +- Form can be anywhere in the document +- Submit with Enter works +- FormData collection works +- Accessible by default + +## Default Values and Reset + +Forms support `defaultValue` for easy resets: + +```svelte + + +
(name = '')}> + + + +
+``` + +## Progressive Enhancement + +```svelte + + +
{ + submitting = true; + return async ({ update }) => { + await update(); + submitting = false; + }; + }} +> + + +
+``` + +## Form Validation with Valibot + +```typescript +// +page.server.ts +import * as v from 'valibot'; +import { fail } from '@sveltejs/kit'; + +const ContactSchema = v.object({ + email: v.pipe(v.string(), v.email()), + message: v.pipe(v.string(), v.minLength(10)), +}); + +export const actions = { + default: async ({ request }) => { + const formData = await request.formData(); + const data = Object.fromEntries(formData); + + const result = v.safeParse(ContactSchema, data); + + if (!result.success) { + return fail(400, { + data, + errors: v.flatten(result.issues), + }); + } + + // Process valid data + await saveContact(result.output); + }, +}; +``` + +```svelte + + + +
+ + + + + +
+``` + +## Multiple Forms on One Page + +```svelte +
+ + +
+ +
+ + +
+``` + +```typescript +// +page.server.ts +export const actions = { + subscribe: async ({ request }) => { + // Handle subscription + }, + contact: async ({ request }) => { + // Handle contact + }, +}; +``` diff --git a/packages/autoskills/skills-registry/svelte-runes/svelte-components/references/web-components.md b/packages/autoskills/skills-registry/svelte-runes/svelte-components/references/web-components.md new file mode 100644 index 00000000..af3f77e8 --- /dev/null +++ b/packages/autoskills/skills-registry/svelte-runes/svelte-components/references/web-components.md @@ -0,0 +1,146 @@ +# Web Components with Svelte + +## Basic Setup + +```javascript +// svelte.config.js +export default { + compilerOptions: { + customElement: true, // Enable for entire project + }, +}; +``` + +Or per-component: + +```svelte + + + + +

Hello {name}!

+``` + +## Gotchas + +### 1. Self-Closing Tags + +Svelte 5 requires closing tags. This affects custom elements: + +```svelte + + + + + +``` + +### 2. Nested HTML in Options + +`