diff --git a/.agents/skills/antfu/SKILL.md b/.agents/skills/antfu/SKILL.md new file mode 100644 index 0000000..148183c --- /dev/null +++ b/.agents/skills/antfu/SKILL.md @@ -0,0 +1,204 @@ +--- +name: antfu +description: Anthony Fu's opinionated tooling and conventions for JavaScript/TypeScript projects. Use when setting up new projects, configuring ESLint/Prettier alternatives, monorepos, library publishing, or when the user mentions Anthony Fu's preferences. +metadata: + author: Anthony Fu + version: "2026.09.30" +--- + +> Reference template: [antfu/starter-ts](https://github.com/antfu/starter-ts). When scaffolding a new TypeScript project, mirror its `package.json` scripts, `eslint.config.js`, `knip.json`, `tsdown.config.ts` and workflows rather than inventing a new layout. + +## Coding Practices + +### Code Organization + +- **Single responsibility**: Each source file should have a clear, focused scope/purpose +- **Split large files**: Break files when they become large or handle too many concerns +- **Type separation**: Always separate types and interfaces into `types.ts` or `types/*.ts` +- **Constants extraction**: Move constants to a dedicated `constants.ts` file + +### Runtime Environment + +- **Prefer isomorphic code**: Write runtime-agnostic code that works in Node, browser, and workers whenever possible +- **Clear runtime indicators**: When code is environment-specific, add a comment at the top of the file: + +```ts +// @env node +// @env browser +``` + +### TypeScript + +- **Explicit return types**: Declare return types explicitly when possible +- **Avoid complex inline types**: Extract complex types into dedicated `type` or `interface` declarations + +### Explicitness + +Favor explicit, traceable code over implicit "magic". A reader (human or agent) should be able to follow where every name comes from without running tooling. + +- **Explicit imports**: Prefer explicit `import` statements. Avoid auto-imports — when a framework provides them (e.g. Nuxt/Nitro), turn them off for new projects (see [app-development](references/app-development.md)). +- **No path aliases by default**: Use relative imports (`./foo`, `../bar`). Only use path aliases (`@/`, `~/`, `#imports`, etc.) when they are *already* configured in the project; don't introduce new ones for greenfield code. + +### Comments + +- **Avoid unnecessary comments**: Code should be self-explanatory +- **Explain "why" not "how"**: Comments should describe the reasoning or intent, not what the code does + +### Testing (Vitest) + +- Test files: `foo.ts` → `foo.test.ts` (same directory) +- Use `describe`/`it` API (not `test`) +- Use `toMatchSnapshot` for complex outputs +- Use `toMatchFileSnapshot` with explicit path for language-specific snapshots + +--- + +## Tooling Choices + +### @antfu/ni Commands + +| Command | Description | +|---------|-------------| +| `ni` | Install dependencies | +| `ni ` / `ni -D ` | Add dependency / dev dependency | +| `nr +``` + +### Storybook for Components + +Set up Storybook for component development. Expressing each state as a story keeps components side-effect-free and their states predictable. + +Run the story tests in CI. Prefer the Vitest addon (`@storybook/addon-vitest`) so stories run as part of the existing `vitest` run (see [setting-up](setting-up.md)). diff --git a/.agents/skills/antfu/references/library-development.md b/.agents/skills/antfu/references/library-development.md new file mode 100644 index 0000000..298477b --- /dev/null +++ b/.agents/skills/antfu/references/library-development.md @@ -0,0 +1,161 @@ +--- +name: library-development +description: Building and publishing TypeScript libraries with tsdown. Use when creating npm packages, configuring library bundling, or setting up package.json exports. +--- + +# Library Development + +| Aspect | Choice | +|--------|--------| +| Bundler | tsdown | +| Output | Pure ESM only (no CJS) | +| DTS | Generated via tsdown | +| Exports | Auto-generated via tsdown | +| Package validation | publint (via tsdown) | +| Publishing | npm Trusted Publishing (OIDC) from CI | + +## tsdown Configuration + +Use tsdown with these options enabled: + +```ts +// tsdown.config.ts +import { defineConfig } from 'tsdown' + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['esm'], + dts: true, + exports: true, + publint: true, +}) +``` + +| Option | Value | Purpose | +|--------|-------|---------| +| `format` | `['esm']` | Pure ESM, no CommonJS | +| `dts` | `true` | Generate `.d.ts` files | +| `exports` | `true` | Auto-update `exports` field in `package.json` | +| `publint` | `true` | Validate `package.json`/`exports` on every build (requires `publint` dev dependency) | + +### Multiple Entry Points + +```ts +export default defineConfig({ + entry: [ + 'src/index.ts', + 'src/utils.ts', + ], + format: ['esm'], + dts: true, + exports: true, +}) +``` + +The `exports: true` option auto-generates the `exports` field in `package.json` when running `tsdown`. + +--- + +## API Stability + +For published libraries, lock the public API surface so accidental breaking changes appear as a diff in code review. + +| Tool | Purpose | +|------|---------| +| [`tsnapi`](https://github.com/antfu/tsnapi) | Snapshots runtime exports + type declarations into committed `.snapshot.js` / `.snapshot.d.ts` files via Vitest | +| [`tsdown-stale-guard`](https://github.com/antfu-collective/tsdown-stale-guard) | Records build input/output hashes so tests fail fast when run against a stale build | + +Install both as dev dependencies. Wire `tsdown-stale-guard` as a tsdown plugin so every build records its hash: + +```ts +// tsdown.config.ts +import { defineConfig } from 'tsdown' +import { StaleGuardRecorder } from 'tsdown-stale-guard' + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['esm'], + dts: true, + exports: true, + plugins: [ + StaleGuardRecorder(), + ], +}) +``` + +Snapshot the public API from a Vitest test — prefer the Vitest helpers over the tsdown/Rolldown plugin so build config stays focused on building. Each file gates itself on a fresh build via a per-file `beforeAll(guardStaleBuild)` so the committed `dist/` can never drift from source: + +```ts +// test/api.test.ts (single package) +import { beforeAll } from 'vitest' +import { snapshotApiPerEntry } from 'tsnapi/vitest' +import { guardStaleBuild } from 'tsdown-stale-guard' + +beforeAll(async () => { + await guardStaleBuild() +}) + +await snapshotApiPerEntry(new URL('..', import.meta.url).pathname) +``` + +For monorepos, wrap `describePackagesApiSnapshots()` in a `describe` so the per-package suites share a single stale-build gate: + +```ts +// test/api.test.ts (monorepo, run from root) +import { beforeAll, describe } from 'vitest' +import { describePackagesApiSnapshots } from 'tsnapi/vitest' +import { guardStaleBuild } from 'tsdown-stale-guard' + +describe('packages api', async () => { + beforeAll(async () => { + await guardStaleBuild() + }) + + await describePackagesApiSnapshots() +}) +``` + +### Updating snapshots + +After an intentional API change, rebuild and update both snapshots in one go: + +```bash +nr build # regenerates dist/ and the stale-guard hash +nr test -u # updates the .snapshot.js / .snapshot.d.ts files +``` + +--- + +## package.json + +Required fields for pure ESM library: + +```json +{ + "type": "module", + "sideEffects": false, + "types": "./dist/index.d.mts", + "files": ["dist"], + "scripts": { + "build": "tsdown", + "dev": "tsdown --watch", + "lint": "eslint --cache", + "typecheck": "tsc", + "knip": "knip", + "test": "pnpm run build && vitest", + "ci": "pnpm run lint && pnpm run typecheck && pnpm run knip && pnpm run test --run", + "prepack": "nr build", + "release": "bumpp" + } +} +``` + +The `exports` field is managed by tsdown when `exports: true`. Use `bumpp -r` in monorepos. + +### prepack Script + +For each public package, add `"prepack": "nr build"` to `scripts`. This ensures the package is automatically built before publishing (e.g., when running `npm publish` or `pnpm publish`). This prevents accidentally publishing stale or missing build artifacts. + +### Releasing + +Run `nr ci` first — it must pass. Then `nr release`: `bumpp` bumps the version, commits, tags and pushes. The `v*` tag triggers `release.yml`, which builds and publishes via npm Trusted Publishing (OIDC). Do not publish from a local machine after the initial package creation; see [setting-up](setting-up.md#publishing-with-npm-trusted-publishing-oidc). diff --git a/.agents/skills/antfu/references/monorepo.md b/.agents/skills/antfu/references/monorepo.md new file mode 100644 index 0000000..1bfdde5 --- /dev/null +++ b/.agents/skills/antfu/references/monorepo.md @@ -0,0 +1,126 @@ +--- +name: monorepo +description: Monorepo setup with pnpm workspaces, centralized aliases, and Turborepo. Use when creating or managing multi-package repositories. +--- + +# Monorepo Setup + +## pnpm Workspaces + +Use pnpm workspaces for monorepo management: + +```yaml +# pnpm-workspace.yaml +packages: + - 'packages/*' +``` + +## Scripts Convention + +Have scripts in each package, and use `-r` (recursive) flag at root, +Enable ESLint cache for faster linting in monorepos. + +```json +// root package.json +{ + "scripts": { + "build": "pnpm run -r build", + "test": "vitest", + "lint": "eslint . --cache --concurrency=auto" + } +} +``` + +In each package's `package.json`, add the scripts. + +```json +// packages/*/package.json +{ + "scripts": { + "build": "tsdown", + "prepack": "pnpm build" + } +} +``` + +## ESLint Cache + + +```json +{ + "scripts": { + "lint": "eslint . --cache --concurrency=auto" + } +} +``` + +## Turborepo (Optional) + +For monorepos with many packages or long build times, use Turborepo for task orchestration and caching. + +See the dedicated Turborepo skill for detailed configuration. + +## Centralized Alias + +Path aliases should be explicit and centralized — this is the deliberate "already set up" case. Outside of these configured aliases, prefer relative imports; don't sprinkle ad-hoc aliases across the codebase. + +For better DX across Vite, Nuxt, Vitest configs, create a centralized `alias.ts` at project root: + +```ts +// alias.ts +import fs from 'node:fs' +import { fileURLToPath } from 'node:url' +import { join, relative } from 'pathe' + +const root = fileURLToPath(new URL('.', import.meta.url)) +const r = (path: string) => fileURLToPath(new URL(`./packages/${path}`, import.meta.url)) + +export const alias = { + '@myorg/core': r('core/src/index.ts'), + '@myorg/utils': r('utils/src/index.ts'), + '@myorg/ui': r('ui/src/index.ts'), + // Add more aliases as needed +} + +// Auto-update tsconfig.alias.json paths +const raw = fs.readFileSync(join(root, 'tsconfig.alias.json'), 'utf-8').trim() +const tsconfig = JSON.parse(raw) +tsconfig.compilerOptions.paths = Object.fromEntries( + Object.entries(alias).map(([key, value]) => [key, [`./${relative(root, value)}`]]), +) +const newRaw = JSON.stringify(tsconfig, null, 2) +if (newRaw !== raw) + fs.writeFileSync(join(root, 'tsconfig.alias.json'), `${newRaw}\n`, 'utf-8') +``` + +Then update the `tsconfig.json` to use the alias file: + +```json +{ + "extends": [ + "./tsconfig.alias.json" + ] +} +``` + +### Using Alias in Configs + +Reference the centralized alias in all config files: + +```ts +// vite.config.ts +import { alias } from './alias' + +export default defineConfig({ + resolve: { alias }, +}) +``` + +```ts +// nuxt.config.ts +import { alias } from './alias' + +export default defineNuxtConfig({ + alias, +}) +``` diff --git a/.agents/skills/antfu/references/setting-up.md b/.agents/skills/antfu/references/setting-up.md new file mode 100644 index 0000000..f454122 --- /dev/null +++ b/.agents/skills/antfu/references/setting-up.md @@ -0,0 +1,134 @@ +--- +name: setting-up +description: Project setup files including .gitignore, GitHub Actions workflows, npm OIDC trusted publishing, and VS Code extensions. Use when initializing new projects or adding CI/editor/release config. +--- + +# Project Setup + +Start from [antfu/starter-ts](https://github.com/antfu/starter-ts); the files below mirror it. + +## .gitignore + +Create when `.gitignore` is not present: + +``` +*.log +*.tgz +.cache +.DS_Store +.eslintcache +.githooks +.idea +.env +.nuxt +.temp +.output +.turbo +cache +coverage +dist +lib-cov +logs +node_modules +skills/npm-* +temp +``` + +## GitHub Actions + +Add these workflows when setting up a new project. Skip if workflows already exist. All use [sxzz/workflows](https://github.com/sxzz/workflows) reusable workflows. + +### Autofix Workflow + +**`.github/workflows/autofix.yml`** - Auto-fix linting on PRs: + +```yaml +name: autofix.ci + +on: [pull_request] + +jobs: + autofix: + uses: sxzz/workflows/.github/workflows/autofix.yml@main + permissions: + contents: read +``` + +### CI Workflow + +**`.github/workflows/ci.yml`** - Run the checks on push/PR: + +```yaml +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: {} + +jobs: + unit-test: + uses: sxzz/workflows/.github/workflows/unit-test.yml@main +``` + +### Release Workflow + +**`.github/workflows/release.yml`** - Publish on tag (library projects only): + +```yaml +name: Release + +on: + push: + tags: + - 'v*' + +jobs: + release: + uses: sxzz/workflows/.github/workflows/release.yml@main + with: + publish: true + permissions: + contents: write + id-token: write +``` + +## Publishing with npm Trusted Publishing (OIDC) + +Prefer [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers) over long-lived `NPM_TOKEN` secrets. The workflow above authenticates with a short-lived GitHub OIDC token (`id-token: write`), so there is nothing to leak or rotate, and every release is built on CI from a tagged commit. + +One-time setup per package: + +1. Run `pnpm publish` locally once so the package exists on npm. +2. Open `https://www.npmjs.com/package//access` and link the GitHub repository + `release.yml` workflow as a trusted publisher. + +After that, `nr release` (`bumpp`) bumps the version, commits, tags and pushes; the tag triggers `release.yml`, which publishes. Never add an `NPM_TOKEN` secret to new projects, and don't run `npm publish` from a local machine for subsequent releases. + +## VS Code Extensions + +Configure in `.vscode/extensions.json`: + +```json +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "antfu.pnpm-catalog-lens", + "antfu.iconify", + "antfu.unocss", + "antfu.slidev", + "vue.volar" + ] +} +``` + +| Extension | Description | +|-----------|-------------| +| `dbaeumer.vscode-eslint` | ESLint integration for linting and formatting | +| `antfu.pnpm-catalog-lens` | Shows pnpm catalog version hints inline | +| `antfu.iconify` | Iconify icon preview and autocomplete | +| `antfu.unocss` | UnoCSS IntelliSense and syntax highlighting | +| `antfu.slidev` | Slidev preview and syntax highlighting | +| `vue.volar` | Vue Language Features | diff --git a/.agents/skills/conventional-commit/SKILL.md b/.agents/skills/conventional-commit/SKILL.md new file mode 100644 index 0000000..3884217 --- /dev/null +++ b/.agents/skills/conventional-commit/SKILL.md @@ -0,0 +1,72 @@ +--- +name: conventional-commit +description: 'Prompt and workflow for generating conventional commit messages using a structured XML format. Guides users to create standardized, descriptive commit messages in line with the Conventional Commits specification, including instructions, examples, and validation.' +--- + +### Instructions + +```xml + This file contains a prompt template for generating conventional commit messages. It provides instructions, examples, and formatting guidelines to help users write standardized, descriptive commit messages in accordance with the Conventional Commits specification. +``` + +### Workflow + +**Follow these steps:** + +1. Run `git status` to review changed files. +2. Run `git diff` or `git diff --cached` to inspect changes. +3. Stage your changes with `git add `. +4. Construct your commit message using the following XML structure. +5. After generating your commit message, Copilot will automatically run the following command in your integrated terminal (no confirmation needed): + +```bash +git commit -m "type(scope): description" +``` + +6. Just execute this prompt and Copilot will handle the commit for you in the terminal. + +### Commit Message Structure + +```xml + + feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert + () + A short, imperative summary of the change + (optional: more detailed explanation) +
(optional: e.g. BREAKING CHANGE: details, or issue references)
+
+``` + +### Examples + +```xml + + feat(parser): add ability to parse arrays + fix(ui): correct button alignment + docs: update README with usage instructions + refactor: improve performance of data processing + chore: update dependencies + feat!: send email on registration (BREAKING CHANGE: email service required) + +``` + +### Validation + +```xml + + Must be one of the allowed types. See https://www.conventionalcommits.org/en/v1.0.0/#specification + Optional, but recommended for clarity. + Required. Use the imperative mood (e.g., "add", not "added"). + Optional. Use for additional context. +
Use for breaking changes or issue references.
+
+``` + +### Final Step + +```xml + + git commit -m "type(scope): description" + Replace with your constructed message. Include body and footer if needed. + +``` diff --git a/.agents/skills/shared-tooling-eslint-prettier/SKILL.md b/.agents/skills/shared-tooling-eslint-prettier/SKILL.md new file mode 100644 index 0000000..7eba92f --- /dev/null +++ b/.agents/skills/shared-tooling-eslint-prettier/SKILL.md @@ -0,0 +1,339 @@ +--- +name: shared-tooling-eslint-prettier +description: ESLint 9/10 flat config with defineConfig(), Prettier v3.0+ shared config, eslint-config-prettier integration, typescript-eslint v8+ projectService +--- + +# ESLint & Prettier + +> **Quick Guide:** ESLint 9+ flat config with `defineConfig()` and `globalIgnores()`. typescript-eslint v8+ with `projectService: true`. Prettier shared config with consistent formatting. eslint-config-prettier to disable conflicting rules. eslint-plugin-only-warn for better DX. +> +> **WARNING**: ESLint 10 was released February 2026 and completely removes .eslintrc support. Migrate to flat config now. + +--- + + + +## CRITICAL: Before Using This Skill + +> **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants) + +**(You MUST use ESLint 9+ flat config with `defineConfig()` from `eslint/config` - NOT legacy .eslintrc)** + +**(You MUST use `globalIgnores()` for explicit global ignore patterns - NOT bare `ignores` property)** + +**(You MUST use typescript-eslint v8+ with `projectService: true` for typed linting)** + +**(You MUST include eslint-plugin-only-warn to convert errors to warnings for better DX)** + +**(You MUST use eslint-config-prettier to disable formatting rules that conflict with Prettier)** + + + +--- + +**Auto-detection:** ESLint 9 flat config, defineConfig, globalIgnores, eslint.config.ts, Prettier config, prettier.config.mjs, eslint-config-prettier, eslint-plugin-only-warn, typescript-eslint projectService, .eslintrc migration + +**When to use:** + +- Setting up ESLint 9/10 flat config (standalone or shared) +- Configuring Prettier with shared config +- Migrating from legacy .eslintrc to flat config +- Integrating ESLint and Prettier (eslint-config-prettier) +- Configuring typescript-eslint v8+ with projectService +- Setting up custom ESLint rules (named exports, import restrictions, type imports) + +**When NOT to use:** + +- Runtime code (this is build-time tooling only) +- CI/CD pipeline configuration +- Git hooks or lint-staged setup +- TypeScript compiler configuration (tsconfig) +- Bundler configuration + +**Detailed Resources:** + +- [examples/core.md](examples/core.md) - Essential flat config + Prettier setup +- [examples/eslint.md](examples/eslint.md) - Advanced ESLint patterns (shared configs, custom rules, ESLint 10 migration) +- [examples/prettier.md](examples/prettier.md) - Advanced Prettier patterns (TS config, experimental options, ignore files) +- [reference.md](reference.md) - Version reference and official documentation links + +--- + + + +## Philosophy + +Linting and formatting should be **fast, consistent, and non-blocking**. Developers should not fight with tools - tools should help catch issues early while staying out of the way during development. + +**Core principles:** + +1. **Warnings, not errors** - Use only-warn to convert lint errors to warnings so developers can iterate without being blocked +2. **Single source of truth** - Shared configs prevent drift between packages and team members +3. **Automate formatting** - Prettier handles all formatting decisions; ESLint handles code quality only +4. **No conflicts** - eslint-config-prettier ensures ESLint and Prettier never disagree + + + +--- + + + +## Core Patterns + +### Pattern 1: ESLint 9+ Flat Config with defineConfig() + +ESLint 9+ uses flat config. The `defineConfig()` helper from `eslint/config` provides type safety and automatic array flattening. `globalIgnores()` explicitly marks global ignore patterns. + +```typescript +// eslint.config.ts — the essential flat config +import js from "@eslint/js"; +import { defineConfig, globalIgnores } from "eslint/config"; +import tseslint from "typescript-eslint"; +import eslintConfigPrettier from "eslint-config-prettier"; +import * as onlyWarnPlugin from "eslint-plugin-only-warn"; + +export default defineConfig( + globalIgnores(["dist/**", "generated/**", "node_modules/**"]), + js.configs.recommended, + eslintConfigPrettier, + tseslint.configs.recommended, + { plugins: { "only-warn": onlyWarnPlugin } }, // must be last +); +``` + +**Key points:** `defineConfig()` auto-flattens arrays (no spread operators needed), `globalIgnores()` prevents ambiguous behavior of bare `ignores`, only-warn converts all preceding errors to warnings, eslint-config-prettier disables formatting rules that conflict with Prettier. + +See [examples/core.md](examples/core.md) for the complete standalone and shared config patterns. + +--- + +### Pattern 2: typescript-eslint v8+ with projectService + +The `projectService` feature (stable since v8) auto-discovers the nearest tsconfig.json for each file, replacing the fragile manual `project` path. + +```typescript +parserOptions: { + projectService: true, + allowDefaultProject: ["*.config.ts", "*.config.mjs"], +}, +``` + +**Key points:** Eliminates need for tsconfig.eslint.json files, faster than manual project configuration, `allowDefaultProject` handles config files not in tsconfig. + +See [examples/core.md](examples/core.md) for full typescript-eslint configuration. + +--- + +### Pattern 3: Shared Config Pattern (Monorepos/Teams) + +For teams or monorepos, extract linting config into a shared package to prevent drift. + +```typescript +// packages/eslint-config/base.ts — shared config +export const baseConfig = defineConfig( + globalIgnores(["dist/**", "generated/**"]), + js.configs.recommended, + eslintConfigPrettier, + tseslint.configs.recommended, + { plugins: { "only-warn": onlyWarnPlugin } }, +); + +// apps/my-app/eslint.config.ts — consuming shared config +export default defineConfig(baseConfig, customRules, { + rules: { "no-console": "warn" }, +}); +``` + +**Key points:** `defineConfig()` auto-flattens so no spread operators needed, single source of truth prevents config drift, TypeScript config file for type checking. + +See [examples/eslint.md](examples/eslint.md) for shared config and custom rules patterns. + +--- + +### Pattern 4: Prettier Shared Config + +Prettier config should be consistent across all packages. Use a shared config for teams. + +```javascript +// prettier.config.mjs +const config = { + printWidth: 100, + semi: true, + singleQuote: false, + bracketSpacing: true, + arrowParens: "always", + endOfLine: "lf", + bracketSameLine: false, + // trailingComma: "all" is the default in Prettier 3.0+ +}; +export default config; +``` + +**Key points:** `trailingComma: "all"` is the default in v3.0+ (don't set it explicitly), `bracketSameLine` replaces deprecated `jsxBracketSameLine`, explicit `endOfLine: "lf"` prevents cross-platform issues. + +See [examples/prettier.md](examples/prettier.md) for TypeScript config files, experimental options, and ignore patterns. + +--- + +### Pattern 5: Custom ESLint Rules + +Common custom rules for enforcing project conventions — named exports, consistent type imports, unused variable detection, and import boundary enforcement: + +```javascript +export const customRules = { + rules: { + "import/no-default-export": "warn", + "@typescript-eslint/consistent-type-imports": [ + "warn", + { prefer: "type-imports" }, + ], + "@typescript-eslint/no-unused-vars": [ + "warn", + { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, + ], + }, +}; +``` + +See [examples/eslint.md](examples/eslint.md) for the full custom rules pattern including import boundary restrictions. + +--- + +### Pattern 6: Using extends Property (ESLint 9.15+) + +The `extends` property in flat config objects simplifies plugin composition — standardizes config merging regardless of plugin format (object, array, or string): + +```typescript +export default defineConfig({ + files: ["**/*.ts", "**/*.tsx"], + extends: [ + "eslint/recommended", + tseslint.configs.recommended, + // Add your framework plugin's flat config here + ], + rules: { "no-console": "warn" }, +}); +``` + +See [examples/core.md](examples/core.md) for the full extends pattern. + + + +--- + + + +## Decision Framework + +### ESLint + Prettier vs Alternatives + +``` +Need linting and formatting? +├─ Speed is critical bottleneck (1000+ files)? +│ └─ YES → Consider a Rust-based unified linter/formatter (not this skill's scope) +└─ Need mature plugin ecosystem? + └─ YES → ESLint 9/10 + Prettier ✓ +``` + +**ESLint + Prettier strengths:** Mature ecosystem, extensive plugin support, framework-specific plugins + +### ESLint 9 vs ESLint 10 + +``` +Which ESLint version? +├─ New project? +│ └─ ESLint 10 (latest, cleanest API) +├─ Existing project with flat config? +│ └─ ESLint 10 (straightforward upgrade) +├─ Existing project with .eslintrc? +│ ├─ Can invest time to migrate? +│ │ └─ YES → Migrate to flat config, then ESLint 10 +│ └─ NO → ESLint 9.x (still supported, but plan migration) +└─ Node.js < 20.19.0? + └─ ESLint 9.x (ESLint 10 requires 20.19.0+) +``` + +### Shared Config vs Local Config + +``` +Setting up linting/formatting? +├─ Monorepo with multiple packages? +│ └─ YES → Shared config ✓ +├─ Team project (2+ developers)? +│ └─ YES → Shared config (consistency matters) +└─ Single package / solo project? + └─ YES → Local config is fine +``` + +### Prettier Config File Format + +``` +What Prettier config format to use? +├─ Need type checking in config? +│ ├─ Node.js 22.6.0+? → prettier.config.ts +│ └─ NO → Use .mjs with JSDoc types +├─ ESM project? → prettier.config.mjs +└─ CommonJS project? → prettier.config.cjs +``` + + + +--- + + + +## RED FLAGS + +**High Priority Issues:** + +- ❌ Using legacy .eslintrc format instead of ESLint 9+ flat config (**BROKEN in ESLint 10**) +- ❌ Using bare `ignores` property instead of `globalIgnores()` helper (ambiguous behavior — acts as global ignores when alone, but as local excludes when paired with other properties) +- ❌ Missing eslint-plugin-only-warn (errors block developers during development) +- ❌ Missing eslint-config-prettier (ESLint and Prettier rules conflict, creating endless fix cycles) + +**Medium Priority Issues:** + +- ⚠️ Using manual `project` option instead of `projectService: true` in typescript-eslint (fragile in monorepos, slower) +- ⚠️ Using deprecated `jsxBracketSameLine` option in Prettier (renamed to `bracketSameLine` in Prettier 2.4) +- ⚠️ Explicitly setting `trailingComma: "all"` in Prettier 3.0+ (it is already the default) +- ⚠️ Using `tseslint.config()` wrapper (planned for deprecation in favor of ESLint's native `defineConfig()`) +- ⚠️ Hardcoded config values in each package instead of shared config + +**Gotchas & Edge Cases:** + +- only-warn plugin must be loaded AFTER other plugins to convert their errors to warnings +- `defineConfig()` auto-flattens arrays — never use spread operators with it +- `projectService` requires typescript-eslint v8+ (was `EXPERIMENTAL_useProjectService` in v6-v7) +- ESLint 10 requires Node.js `^20.19.0 || ^22.13.0 || >=24` (v21.x and v23.x explicitly unsupported) +- ESLint 10 config lookup starts from linted file directory (not cwd) — enables monorepo multi-config +- Prettier TypeScript config files (`.prettierrc.ts`) require Node.js 22.6.0+; before Node v24.3.0 run with `--experimental-strip-types` +- Prettier 3.0+ APIs are async — plugins using sync APIs need migration (use `@prettier/sync` for sync wrappers) +- **ESLint 9.15+**: `eslint.config.ts` TypeScript config files and `extends` property supported natively +- **ESLint 9.34+**: Multithreaded linting available via `--concurrency` flag (30-300% performance boost on large projects) +- **ESLint 10**: `/* eslint-env */` comments now trigger errors; deprecated `Linter` methods removed + + + +--- + + + +## CRITICAL REMINDERS + +> **All code must follow project conventions in CLAUDE.md** + +**(You MUST use ESLint 9+ flat config with `defineConfig()` from `eslint/config` - NOT legacy .eslintrc)** + +**(You MUST use `globalIgnores()` for explicit global ignore patterns - NOT bare `ignores` property)** + +**(You MUST use typescript-eslint v8+ with `projectService: true` for typed linting)** + +**(You MUST include eslint-plugin-only-warn to convert errors to warnings for better DX)** + +**(You MUST use eslint-config-prettier to disable formatting rules that conflict with Prettier)** + +**Failure to follow these rules will cause inconsistent tooling, conflicting formatting rules, and blocked developers.** + +**WARNING: ESLint 10 (February 2026) completely removes .eslintrc support. Plan migration now.** + + diff --git a/.agents/skills/shared-tooling-eslint-prettier/examples/core.md b/.agents/skills/shared-tooling-eslint-prettier/examples/core.md new file mode 100644 index 0000000..bdf8639 --- /dev/null +++ b/.agents/skills/shared-tooling-eslint-prettier/examples/core.md @@ -0,0 +1,172 @@ +# ESLint & Prettier - Core Examples + +> Essential patterns for ESLint 9+ flat config with Prettier integration. See [eslint.md](eslint.md) for shared configs, custom rules, and ESLint 10 migration. See [prettier.md](prettier.md) for TypeScript config files and experimental options. + +--- + +## Pattern 1: Standalone Flat Config + +The minimal production-ready ESLint config combining flat config, typescript-eslint, Prettier, and only-warn: + +```typescript +// eslint.config.ts +import js from "@eslint/js"; +import { defineConfig, globalIgnores } from "eslint/config"; +import tseslint from "typescript-eslint"; +import eslintConfigPrettier from "eslint-config-prettier"; +import * as onlyWarnPlugin from "eslint-plugin-only-warn"; + +export default defineConfig( + // Global ignores using the helper function + globalIgnores(["dist/**", "generated/**", "node_modules/**"]), + + js.configs.recommended, + eslintConfigPrettier, + + // typescript-eslint recommended rules + tseslint.configs.recommended, + + // Convert all errors to warnings for better DX (must be last) + { + plugins: { + "only-warn": onlyWarnPlugin, + }, + }, +); +``` + +**Why good:** `defineConfig()` provides type safety and auto-flattens nested arrays, `globalIgnores()` explicitly marks global ignores (clearer intent than bare `ignores`), only-warn plugin loaded last converts all preceding errors to warnings, eslint-config-prettier disables formatting rules that conflict with Prettier + +```javascript +// BAD: Legacy .eslintrc format (BROKEN in ESLint 10) +// .eslintrc.json (DON'T USE THIS) +{ + "extends": ["eslint:recommended", "prettier"], + "plugins": ["@typescript-eslint"], + "rules": { + "no-unused-vars": "error" + }, + "ignorePatterns": ["dist/"] +} +``` + +**Why bad:** Legacy .eslintrc is deprecated in ESLint 9 and completely removed in ESLint 10 (February 2026), error severity blocks developers during development, no only-warn plugin + +--- + +## Pattern 2: typescript-eslint v8+ with projectService + +```typescript +// eslint.config.ts — adding typed linting +import { defineConfig } from "eslint/config"; +import tseslint from "typescript-eslint"; + +export default defineConfig(tseslint.configs.recommended, { + languageOptions: { + parser: tseslint.parser, + parserOptions: { + // projectService replaces the old project/parserOptions pattern + projectService: true, + // Allows linting files not in tsconfig (like config files) + allowDefaultProject: ["*.config.ts", "*.config.mjs"], + }, + }, +}); +``` + +**Why good:** `projectService` auto-discovers nearest tsconfig.json for each file, `allowDefaultProject` lints config files without adding them to tsconfig, faster than manual project configuration, eliminates need for tsconfig.eslint.json files + +```javascript +// BAD: Manual project option (old approach) +parserOptions: { + project: "./tsconfig.json", // Fragile path, no auto-discovery +} +``` + +**Why bad:** Manual `project` path is fragile in monorepos, requires tsconfig.eslint.json for config files, slower than projectService + +--- + +## Pattern 3: Using extends Property (ESLint 9.15+) + +The `extends` property simplifies plugin composition by standardizing config merging regardless of plugin format: + +```typescript +// eslint.config.ts +import { defineConfig } from "eslint/config"; +import tseslint from "typescript-eslint"; + +export default defineConfig({ + files: ["**/*.ts", "**/*.tsx"], + extends: [ + // String references for standard configs + "eslint/recommended", + // Plugin configs (various formats supported) + tseslint.configs.recommended, + // Add your framework plugin's flat config here + ], + rules: { + // Override specific rules + "no-console": "warn", + }, +}); +``` + +**Why good:** Standardizes config merging regardless of plugin format (object, array, or string), cleaner than spreading arrays manually, conditionally applies configs based on file patterns. Add your framework-specific plugins via `extends` as needed. + +--- + +## Pattern 4: Prettier Standard Config + +```javascript +// prettier.config.mjs (standalone project) +// OR packages/prettier-config/prettier.config.mjs (shared config) +const config = { + printWidth: 100, + useTabs: false, + tabWidth: 2, + semi: true, + singleQuote: false, + // trailingComma: "all" is the default in Prettier 3.0+ + bracketSpacing: true, + arrowParens: "always", + endOfLine: "lf", + // bracketSameLine replaces deprecated jsxBracketSameLine (Prettier 2.4+) + bracketSameLine: false, +}; + +export default config; +``` + +**Why good:** Single source of truth prevents formatting inconsistencies, explicit `endOfLine: "lf"` prevents cross-platform line ending issues, double quotes match JSON format reducing escaping in JSX + +--- + +## Pattern 5: eslint-config-prettier Integration + +eslint-config-prettier disables all ESLint rules that conflict with Prettier formatting. It must be included after other configs: + +```typescript +// eslint.config.ts — correct integration +import { defineConfig } from "eslint/config"; +import eslintConfigPrettier from "eslint-config-prettier"; +import tseslint from "typescript-eslint"; + +export default defineConfig( + tseslint.configs.recommended, + // eslint-config-prettier AFTER other configs to disable conflicting rules + eslintConfigPrettier, +); +``` + +**Why good:** Prevents ESLint from reporting formatting issues that Prettier will handle, eliminates "fix one, break the other" cycles + +```typescript +// BAD: Missing eslint-config-prettier +export default defineConfig( + tseslint.configs.recommended, + // No eslint-config-prettier - ESLint and Prettier will fight over formatting +); +``` + +**Why bad:** ESLint rules like `indent`, `quotes`, `semi` will conflict with Prettier's formatting, creating an endless cycle of conflicting auto-fixes diff --git a/.agents/skills/shared-tooling-eslint-prettier/examples/eslint.md b/.agents/skills/shared-tooling-eslint-prettier/examples/eslint.md new file mode 100644 index 0000000..4e2266a --- /dev/null +++ b/.agents/skills/shared-tooling-eslint-prettier/examples/eslint.md @@ -0,0 +1,159 @@ +# ESLint - Advanced Examples + +> Shared configs, custom rules, and ESLint 10 migration. See [core.md](core.md) for essential flat config setup. + +**Prerequisites**: Understand Pattern 1 (Standalone Flat Config) and Pattern 2 (projectService) from [core.md](core.md) first. + +--- + +## Pattern 6: Shared Config Package + +For teams or monorepos, extract linting config into a shared package: + +```typescript +// packages/eslint-config/base.ts +import js from "@eslint/js"; +import { defineConfig, globalIgnores } from "eslint/config"; +import tseslint from "typescript-eslint"; +import eslintConfigPrettier from "eslint-config-prettier"; +import * as onlyWarnPlugin from "eslint-plugin-only-warn"; + +export const baseConfig = defineConfig( + globalIgnores(["dist/**", "generated/**", "build/**"]), + + js.configs.recommended, + eslintConfigPrettier, + tseslint.configs.recommended, + + // Convert all errors to warnings for better DX + { + plugins: { + "only-warn": onlyWarnPlugin, + }, + }, +); +``` + +**Why good:** Single source of truth prevents config drift, `defineConfig()` provides type safety, explicit `globalIgnores()` for clarity + +--- + +## Pattern 7: Consuming Shared Config + +```typescript +// apps/my-app/eslint.config.ts +import { defineConfig } from "eslint/config"; +import { baseConfig } from "@company/eslint-config"; +import { customRules } from "@company/eslint-config/custom-rules"; + +export default defineConfig(baseConfig, customRules, { + // App-specific overrides + rules: { + "no-console": "warn", + }, +}); +``` + +**Why good:** No spread operators needed with `defineConfig()` (auto-flattens), clean composition of shared configs + +```javascript +// BAD: Manual spreading (old pattern) +export default [ + ...baseConfig, + customRules, + { rules: { "no-console": "warn" } }, +]; +``` + +**Why bad:** Spread operator required for array configs, no type safety, easy to make mistakes with array composition + +--- + +## Pattern 8: Custom ESLint Rules + +Common custom rules for enforcing project conventions: + +```javascript +// packages/eslint-config/custom-rules.js +export const customRules = { + rules: { + // Enforce named exports for better tree-shaking + "import/no-default-export": "warn", + + // Prevent importing from internal package paths + "no-restricted-imports": [ + "error", + { + patterns: [ + { + group: ["@company/*/src/**"], + message: "Import from package exports, not internal paths", + }, + ], + }, + ], + + // Enforce import type for type-only imports + "@typescript-eslint/consistent-type-imports": [ + "warn", + { prefer: "type-imports" }, + ], + + // Catch unused variables with underscore escape hatch + "@typescript-eslint/no-unused-vars": [ + "warn", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + }, + ], + }, +}; +``` + +**Why good:** Named exports enable better tree-shaking, preventing internal imports maintains package API boundaries, consistent type imports improve build performance, unused variable warnings catch dead code early with underscore escape hatch + +--- + +## ESLint 10 Migration + +ESLint 10 was released February 6, 2026 and completely removes .eslintrc support. Before upgrading: + +1. **Remove .eslintrc files** - Replace with `eslint.config.ts` +2. **Remove .eslintignore** - Use `globalIgnores()` in config +3. **Update CLI scripts** - Remove `--no-eslintrc`, `--env`, `--rulesdir` flags +4. **Remove `/* eslint-env */` comments** - These now trigger errors in ESLint 10 +5. **Update Node.js** - ESLint 10 requires `^20.19.0 || ^22.13.0 || >=24` + +**Key ESLint 10 changes:** + +- Config lookup starts from linted file directory (not cwd) - better monorepo support +- JSX reference tracking improved - fewer false positives with `no-unused-vars` +- Updated `eslint:recommended` with new rules +- Deprecated `Linter` methods removed (`defineParser()`, `defineRule()`, `getRules()`) +- Built-in TypeScript definitions (no more `@types/eslint` needed) + +**ESLint 10 compatible config:** + +```typescript +// eslint.config.ts - works with both ESLint 9.15+ and ESLint 10 +import js from "@eslint/js"; +import { defineConfig, globalIgnores } from "eslint/config"; +import tseslint from "typescript-eslint"; +import eslintConfigPrettier from "eslint-config-prettier"; +import * as onlyWarnPlugin from "eslint-plugin-only-warn"; + +export default defineConfig( + globalIgnores(["dist/**", "node_modules/**"]), + js.configs.recommended, + eslintConfigPrettier, + tseslint.configs.recommended, + { + plugins: { + "only-warn": onlyWarnPlugin, + }, + }, +); +``` + +**Why good:** This config works with both ESLint 9.15+ and ESLint 10 with zero changes — `defineConfig()` and `globalIgnores()` are the forward-compatible API diff --git a/.agents/skills/shared-tooling-eslint-prettier/examples/prettier.md b/.agents/skills/shared-tooling-eslint-prettier/examples/prettier.md new file mode 100644 index 0000000..9e6bcb4 --- /dev/null +++ b/.agents/skills/shared-tooling-eslint-prettier/examples/prettier.md @@ -0,0 +1,137 @@ +# Prettier - Advanced Examples + +> TypeScript config files, experimental options, shared config usage, and ignore patterns. See [core.md](core.md) for the standard Prettier config. + +**Prerequisites**: Understand Pattern 4 (Prettier Standard Config) from [core.md](core.md) first. + +--- + +## Pattern 9: Shared Config Usage + +Reference a shared Prettier config from `package.json` to prevent per-package config drift: + +```json +// apps/my-app/package.json +{ + "name": "my-app", + "prettier": "@company/prettier-config", + "devDependencies": { + "@company/prettier-config": "*" + } +} +``` + +**Why good:** Single source of truth, no per-package formatting inconsistencies, zero config in each app + +```json +// BAD: Duplicated config in each package +// apps/client/.prettierrc +{ "printWidth": 80, "semi": true, "singleQuote": true } + +// apps/dashboard/.prettierrc +{ "printWidth": 120, "semi": false, "singleQuote": true } +``` + +**Why bad:** Different configs per package creates inconsistent formatting, developers switching between packages see formatting churn, code reviews show formatting noise + +--- + +## Pattern 10: TypeScript Config (v3.5+) + +Prettier 3.5+ supports TypeScript configuration files for type-safe config. Requires Node.js 22.6.0+. + +```typescript +// packages/prettier-config/prettier.config.ts +import type { Config } from "prettier"; + +const config: Config = { + printWidth: 100, + useTabs: false, + tabWidth: 2, + semi: true, + singleQuote: false, + bracketSpacing: true, + arrowParens: "always", + endOfLine: "lf", + bracketSameLine: false, +}; + +export default config; +``` + +**Requirements:** + +- Node.js 22.6.0 or later +- Before Node.js v24.3.0, run with: `NODE_OPTIONS="--experimental-strip-types" prettier . --write` + +**Supported file names:** `.prettierrc.ts`, `.prettierrc.mts`, `.prettierrc.cts`, `prettier.config.ts`, `prettier.config.mts`, `prettier.config.cts` + +--- + +## Pattern 11: Experimental Options (v3.1+) + +Experimental options address long-standing formatting debates. These may be removed or changed in future versions. + +```javascript +// prettier.config.mjs +const config = { + printWidth: 100, + semi: true, + singleQuote: false, + bracketSpacing: true, + + // Experimental: ternary formatting (v3.1+) + experimentalTernaries: true, + + // Experimental: object wrapping (v3.5+) + // "preserve" (default): keeps multi-line objects as-is + // "collapse": collapses objects that fit on one line + objectWrap: "preserve", + + // Experimental: operator position (v3.5+) + // "end" (default): operators at end of line + // "start": operators at start of new lines + experimentalOperatorPosition: "end", +}; + +export default config; +``` + +--- + +## Pattern 12: Prettier Ignore Patterns + +``` +# .prettierignore +dist/ +build/ +coverage/ +node_modules/ +*.min.js +*.min.css +pnpm-lock.yaml +package-lock.json +bun.lockb +``` + +**Why good:** Prevents Prettier from touching generated files, lock files, and minified assets where formatting is irrelevant or harmful + +--- + +## Common Prettier Options + +| Option | Default (v3.0+) | Notes | +| ------------------------------ | --------------- | ------------------------------ | +| `printWidth` | `80` | Consider 100 for wider screens | +| `tabWidth` | `2` | | +| `useTabs` | `false` | | +| `semi` | `true` | | +| `singleQuote` | `false` | | +| `trailingComma` | `"all"` | Changed from `"es5"` in v3.0 | +| `bracketSpacing` | `true` | | +| `bracketSameLine` | `false` | Replaces `jsxBracketSameLine` | +| `arrowParens` | `"always"` | | +| `endOfLine` | `"lf"` | | +| `objectWrap` | `"preserve"` | v3.5+ experimental | +| `experimentalTernaries` | `false` | v3.1+ experimental | +| `experimentalOperatorPosition` | `"end"` | v3.5+ experimental | diff --git a/.agents/skills/shared-tooling-eslint-prettier/metadata.yaml b/.agents/skills/shared-tooling-eslint-prettier/metadata.yaml new file mode 100644 index 0000000..4582b05 --- /dev/null +++ b/.agents/skills/shared-tooling-eslint-prettier/metadata.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/agents-inc/cli/main/src/schemas/metadata.schema.json +category: shared-lint +slug: eslint-prettier +domain: shared +author: "@vince" +displayName: ESLint & Prettier +cliDescription: ESLint 9/10 flat config and Prettier formatting +usageGuidance: Use when configuring ESLint 9 flat config, Prettier formatting, shared linting configs, or migrating from legacy .eslintrc to flat config. diff --git a/.agents/skills/shared-tooling-eslint-prettier/reference.md b/.agents/skills/shared-tooling-eslint-prettier/reference.md new file mode 100644 index 0000000..5b8ffc9 --- /dev/null +++ b/.agents/skills/shared-tooling-eslint-prettier/reference.md @@ -0,0 +1,46 @@ +# ESLint & Prettier Reference + +> Version reference and official documentation links. See [SKILL.md](SKILL.md) for decision frameworks and red flags. + +--- + +## Version Reference + +| Tool | Latest Stable | Key Feature | +| ----------------------- | ------------- | -------------------------------------------------- | +| ESLint 9 | v9.39.4 | Flat config, defineConfig(), multithreaded linting | +| ESLint 10 | v10.0.3 | .eslintrc removed, file-based config lookup | +| Prettier | v3.8.1 | TS config files, experimental options | +| typescript-eslint | v8.57.1 | projectService (stable), shared configs | +| eslint-config-prettier | latest | Disables conflicting ESLint formatting rules | +| eslint-plugin-only-warn | latest | Converts errors to warnings | + +--- + +## Prettier Config File Precedence + +Highest to lowest priority: + +1. `"prettier"` key in `package.json` +2. `.prettierrc` (JSON/YAML) +3. `.prettierrc.json`, `.prettierrc.yaml` +4. `.prettierrc.js`, `prettier.config.js` +5. `.prettierrc.mjs`, `prettier.config.mjs` +6. `.prettierrc.cjs`, `prettier.config.cjs` +7. `.prettierrc.ts`, `prettier.config.ts` (v3.5+) +8. `.prettierrc.toml` + +--- + +## Official Documentation + +- [ESLint Flat Config](https://eslint.org/docs/latest/use/configure/configuration-files) +- [ESLint Migration Guide](https://eslint.org/docs/latest/use/configure/migration-guide) +- [ESLint 10 Release](https://eslint.org/blog/2026/02/eslint-v10.0.0-released/) +- [ESLint defineConfig blog post](https://eslint.org/blog/2025/03/flat-config-extends-define-config-global-ignores/) +- [ESLint Multithreaded Linting](https://eslint.org/blog/2025/08/multithread-linting/) +- [typescript-eslint v8](https://typescript-eslint.io/blog/announcing-typescript-eslint-v8/) +- [typescript-eslint projectService](https://typescript-eslint.io/troubleshooting/typed-linting/) +- [Prettier Options](https://prettier.io/docs/en/options.html) +- [Prettier Configuration](https://prettier.io/docs/en/configuration.html) +- [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) diff --git a/.claude/skills/antfu b/.claude/skills/antfu new file mode 120000 index 0000000..4fc0071 --- /dev/null +++ b/.claude/skills/antfu @@ -0,0 +1 @@ +../../.agents/skills/antfu \ No newline at end of file diff --git a/.claude/skills/conventional-commit b/.claude/skills/conventional-commit new file mode 120000 index 0000000..6479df2 --- /dev/null +++ b/.claude/skills/conventional-commit @@ -0,0 +1 @@ +../../.agents/skills/conventional-commit \ No newline at end of file diff --git a/.claude/skills/shared-tooling-eslint-prettier b/.claude/skills/shared-tooling-eslint-prettier new file mode 120000 index 0000000..930d5df --- /dev/null +++ b/.claude/skills/shared-tooling-eslint-prettier @@ -0,0 +1 @@ +../../.agents/skills/shared-tooling-eslint-prettier \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index dd12802..2bf67f7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,7 +5,7 @@ - **Language / Runtime**: TypeScript, Node 20 - **Framework**: Next.js 16 (App Router, React 19) in `client/`; Express 5 in `backend/` - **Key dependencies**: Prisma 7 on Postgres (Neon), Better Auth, Zod, Tailwind CSS 4 + shadcn/ui, pino -- **Package manager**: npm (no workspaces; `client/` and `backend/` install separately) +- **Package manager**: npm (no workspaces; install in the root for tooling, plus `client/` and `backend/`) ## Build approach @@ -14,7 +14,7 @@ Tracer Bullet (one thin real path through every layer first, then thicken one se ## Commands ```bash -# Install (run once in client/ and once in backend/) +# Install (run once in the root, client/, and backend/) npm install # Dev: client on 3000, backend on 4000, Next rewrites /api/* to the backend @@ -23,7 +23,13 @@ npm run dev # Build and start (same scripts in both apps) npm run build && npm run start +# Root only (tooling) +npm run format # prettier --write . +npm run format:check # prettier --check . +npm run verify # format + lint + typecheck; what the pre-commit hook runs + # Backend only +npm run lint # eslint npm run typecheck # tsc --noEmit npm test # vitest run npm run db:migrate # prisma migrate dev @@ -31,8 +37,11 @@ npm run seed # first accounts # Client only npm run lint # eslint +npm run typecheck # next typegen && tsc --noEmit ``` +Backend note: in `backend/`, the `typescript` package is aliased to the TypeScript 6 API so typescript-eslint can load, and `tsc` comes from `@typescript/native` (TypeScript 7). + ## Specs Stored in `docs/specs/`. Format: `docs/specs/NNNN-title.md`. @@ -46,7 +55,7 @@ Stored in `docs/specs/`. Format: `docs/specs/NNNN-title.md`. - Folder by feature for new code; the scaffolded `routes/`/`lib/`/`config/` layout in `backend/` stays as it is. - Before every commit: lint, format, typecheck; the pre-commit hook blocks the commit when one fails. - Test gate before a feature is done: unit plus integration; browser tests come with the features that need them. -- CI on push: lint, typecheck, test on GitHub. +- CI on push: format check, lint, typecheck, test on GitHub. - General craft: consistent error handling, consistent naming, documented public APIs, conventional commit messages, named exports only, DRY, YAGNI, reusability, modularity, performance first, accessibility baseline (WCAG AA). - server components for all root pages in client/ - separation of concerns (hooks hold logic, data holds data, components render view) @@ -58,11 +67,13 @@ Stored in `docs/specs/`. Format: `docs/specs/NNNN-title.md`. - integration: on - branch prefix: feat/, fix/, chore/ - commit: per-milestone +- enforced by hooks: pre-commit runs the branch prefix check plus `npm run verify`; commit-msg runs commitlint (conventional messages) ## Agent skills Load only the ones the current task needs. +- [antfu](.agents/skills/antfu/): `antfu/skills`, opinionated lint and format workflow conventions for JS/TS - [api-and-interface-design](.agents/skills/api-and-interface-design/): `addyosmani/agent-skills`, API surface and interface design - [api-design-principles](.agents/skills/api-design-principles/): `wshobson/agents`, REST API design - [better-auth-best-practices](.agents/skills/better-auth-best-practices/): `better-auth/skills`, Better Auth config, adapters, sessions @@ -72,6 +83,7 @@ Load only the ones the current task needs. - [cloudinary-next](.agents/skills/cloudinary-next/): `cloudinary-devs/skills`, Cloudinary with Next.js - [cloudinary-react](.agents/skills/cloudinary-react/): `cloudinary-devs/skills`, Cloudinary React SDK - [cloudinary-transformations](.agents/skills/cloudinary-transformations/): `cloudinary-devs/skills`, transformation URLs +- [conventional-commit](.agents/skills/conventional-commit/): `github/awesome-copilot`, authoring conventional commit messages - [create-auth](.agents/skills/create-auth/): `better-auth/skills`, scaffolding auth flows - [deploy-to-vercel](.agents/skills/deploy-to-vercel/): `vercel-labs/agent-skills`, Vercel deploys - [e2e-testing-patterns](.agents/skills/e2e-testing-patterns/): `wshobson/agents`, E2E suite structure @@ -96,6 +108,7 @@ Load only the ones the current task needs. - [sentry-nextjs-sdk](.agents/skills/sentry-nextjs-sdk/): `getsentry/sentry-agent-skills`, `@sentry/nextjs` setup - [sentry-react-sdk](.agents/skills/sentry-react-sdk/): `getsentry/sentry-agent-skills`, Sentry React SDK - [shadcn](.agents/skills/shadcn/): `shadcn-ui/ui`, shadcn components +- [shared-tooling-eslint-prettier](.agents/skills/shared-tooling-eslint-prettier/): `agents-inc/skills`, ESLint 9 flat config and Prettier v3 shared setup for TS projects - [tailwind-design-system](.agents/skills/tailwind-design-system/): `wshobson/agents`, Tailwind tokens and design system - [tailwind-v4-shadcn](.agents/skills/tailwind-v4-shadcn/): `secondsky/claude-skills`, Tailwind 4 with shadcn - [two-factor-authentication-best-practices](.agents/skills/two-factor-authentication-best-practices/): `better-auth/skills`, TOTP, OTP, backup codes diff --git a/docs/scope/scope.md b/docs/scope/scope.md index 7a22001..1ee0f5a 100644 --- a/docs/scope/scope.md +++ b/docs/scope/scope.md @@ -14,7 +14,7 @@ _These are recommendations to keep your build orderly, not requirements. Skip an | A | Client scaffold | Foundation | existing | | B | Backend scaffold | Foundation | in-progress | | 18 | Stack & architecture | Foundation | in-progress | -| 1 | Coding standards & tooling | Foundation | in-progress | +| 1 | Coding standards & tooling | Foundation | done | | 2 | Data model | Foundation | planned | | 3 | Design system & UI foundation | Foundation | planned | | 4 | Auth & roles | Slice 1 | planned | @@ -49,12 +49,12 @@ Execute the stack decision from spec 0001: one git repo at the root, both apps b - [x] Test it: `/test stack & architecture` Spec [0001](../specs/0001-stack-and-architecture/index.md) · code in `backend/`, `client/`, `render.yaml`, `docs/deploy.md` -### 1. Coding standards & tooling · in-progress +### 1. Coding standards & tooling · done Capture conventions from the real scaffold, then install lint, format, and pre commit checks. There is no root `AGENTS.md` yet, so this is the first stop for real context. **Done when:** root `AGENTS.md` reflects the real stack, and lint, format, and pre commit checks run clean. - [x] Capture conventions and tooling choices: `/audit` - [x] Install the tooling: `/develop tooling` -- [ ] Check it runs clean: `/test` +- [x] Check it runs clean: `/test` Tooling in `package.json`, `.prettierrc.json`, `.husky/pre-commit`, `.husky/commit-msg`, `commitlint.config.js`, `.github/workflows/ci.yml`, `backend/eslint.config.mjs`, `backend/vitest.config.ts`, `AGENTS.md` ### 2. Data model · needs a decision diff --git a/skills-lock.json b/skills-lock.json index 37f6076..07b130a 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -1,6 +1,12 @@ { "version": 1, "skills": { + "antfu": { + "source": "antfu/skills", + "sourceType": "github", + "skillPath": "skills/antfu/SKILL.md", + "computedHash": "46935e4dc837849a1df6fa968c3ec32c647bd0faecf34f7a3577503f85d7bc52" + }, "api-and-interface-design": { "source": "addyosmani/agent-skills", "sourceType": "github", @@ -55,6 +61,12 @@ "skillPath": "skills/platform/cloudinary-transformations/SKILL.md", "computedHash": "2f42062f667b53e99339e5c30e8ff0c2af80913ed5e8ccde2e2c2f4adfff7b92" }, + "conventional-commit": { + "source": "github/awesome-copilot", + "sourceType": "github", + "skillPath": "skills/conventional-commit/SKILL.md", + "computedHash": "f29c9486cede6c7b2df0cfb0a2e4aa67446552b991bcf17d1b309e903171f03d" + }, "create-auth": { "source": "better-auth/skills", "sourceType": "github", @@ -199,6 +211,12 @@ "skillPath": "skills/shadcn/SKILL.md", "computedHash": "f615de216e085a84e31593ad0fa9062232adc40cd87b7c7ab2b8e49abf6500be" }, + "shared-tooling-eslint-prettier": { + "source": "agents-inc/skills", + "sourceType": "github", + "skillPath": "src/skills/shared-tooling-eslint-prettier/SKILL.md", + "computedHash": "532c3bd7f25fef4cc1d59790c0870aab50b8ef663b3e430fd92fbfeb5e11f283" + }, "tailwind-design-system": { "source": "wshobson/agents", "sourceType": "github",