Initial commit
This commit is contained in:
1023
skills/code-style/EXAMPLES.md
Normal file
1023
skills/code-style/EXAMPLES.md
Normal file
File diff suppressed because it is too large
Load Diff
586
skills/code-style/REFERENCE.md
Normal file
586
skills/code-style/REFERENCE.md
Normal file
@@ -0,0 +1,586 @@
|
||||
# Technical Reference
|
||||
|
||||
Complete configuration files and detailed technical information for Grey Haven coding standards.
|
||||
|
||||
## TypeScript/React Configuration Files
|
||||
|
||||
### .prettierrc
|
||||
|
||||
Complete Prettier configuration from cvi-template:
|
||||
|
||||
```json
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"printWidth": 90,
|
||||
"singleQuote": false,
|
||||
"endOfLine": "lf",
|
||||
"trailingComma": "all",
|
||||
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"]
|
||||
}
|
||||
```
|
||||
|
||||
**Field Explanations:**
|
||||
|
||||
- `tabWidth: 2` - Use 2 spaces for indentation (NOT 4)
|
||||
- `semi: true` - Always add semicolons at the end of statements
|
||||
- `printWidth: 90` - Wrap lines at 90 characters (NOT 80 or 120)
|
||||
- `singleQuote: false` - Use double quotes for strings
|
||||
- `endOfLine: "lf"` - Use Unix-style line endings (\\n)
|
||||
- `trailingComma: "all"` - Add trailing commas wherever possible
|
||||
- `prettier-plugin-organize-imports` - Auto-organize imports by type
|
||||
- `prettier-plugin-tailwindcss` - Auto-sort Tailwind CSS classes
|
||||
|
||||
### .eslintrc
|
||||
|
||||
Complete ESLint configuration from cvi-template:
|
||||
|
||||
```json
|
||||
{
|
||||
"root": true,
|
||||
"env": { "browser": true, "es2020": true },
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react-hooks/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["react-refresh"],
|
||||
"rules": {
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"no-unused-vars": "off",
|
||||
"react-refresh/only-export-components": "off"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Rule Explanations:**
|
||||
|
||||
- `react-hooks/exhaustive-deps: "off"` - Don't enforce exhaustive deps in useEffect (manage manually)
|
||||
- `@typescript-eslint/no-explicit-any: "off"` - Allow `any` type for flexibility
|
||||
- `@typescript-eslint/no-unused-vars: "off"` - Don't error on unused variables (clean up manually)
|
||||
- `no-unused-vars: "off"` - Same as above for non-TypeScript files
|
||||
- `react-refresh/only-export-components: "off"` - Allow exporting non-components from module
|
||||
|
||||
**Philosophy:** Grey Haven takes a pragmatic approach over pedantic enforcement. The codebase values developer velocity and allows `any` types, unused variables, and manual dependency management when appropriate.
|
||||
|
||||
### tsconfig.json
|
||||
|
||||
Complete TypeScript configuration from cvi-template:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"jsx": "react-jsx",
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./src/*"]
|
||||
},
|
||||
"noEmit": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", ".wrangler", ".output"]
|
||||
}
|
||||
```
|
||||
|
||||
**Key Settings:**
|
||||
|
||||
- `strict: true` - Enable all strict type checking options
|
||||
- `target: "ES2022"` - Compile to ES2022 JavaScript
|
||||
- `module: "ESNext"` - Use ESNext module syntax
|
||||
- `moduleResolution: "Bundler"` - Use bundler resolution for Vite/TanStack
|
||||
- `jsx: "react-jsx"` - Use React 17+ JSX transform
|
||||
- `paths: { "~/*": ["./src/*"] }` - Path alias for imports (e.g., `import { foo } from "~/lib/utils"`)
|
||||
- `noEmit: true` - Don't emit compiled files (Vite handles this)
|
||||
|
||||
### package.json Scripts
|
||||
|
||||
Standard scripts from cvi-template:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "vinxi dev",
|
||||
"build": "vinxi build",
|
||||
"start": "vinxi start",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:studio": "drizzle-kit studio",
|
||||
"prepare": "husky"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Python/FastAPI Configuration Files
|
||||
|
||||
### pyproject.toml (Ruff)
|
||||
|
||||
Complete Ruff configuration from cvi-backend-template:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
fix-only = true
|
||||
show-fixes = true
|
||||
indent-width = 4
|
||||
line-length = 130 # NOT 80 or 88!
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"E", # pycodestyle errors
|
||||
"W", # pycodestyle warnings
|
||||
"F", # pyflakes
|
||||
"I", # isort
|
||||
"B", # flake8-bugbear
|
||||
"C4", # flake8-comprehensions
|
||||
"UP", # pyupgrade
|
||||
]
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "double"
|
||||
indent-style = "space"
|
||||
line-ending = "lf"
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
known-first-party = ["app"]
|
||||
```
|
||||
|
||||
**Field Explanations:**
|
||||
|
||||
- `line-length = 130` - Wrap lines at 130 characters (CRITICAL: NOT 80 or 88!)
|
||||
- `indent-width = 4` - Use 4 spaces for indentation
|
||||
- `fix-only = true` - Auto-fix issues without showing unfixable errors
|
||||
- `show-fixes = true` - Show what was fixed
|
||||
- `select = [...]` - Enable specific linter rules:
|
||||
- `E`, `W` - pycodestyle style enforcement
|
||||
- `F` - pyflakes error detection
|
||||
- `I` - isort import sorting
|
||||
- `B` - flake8-bugbear bug detection
|
||||
- `C4` - flake8-comprehensions list/dict comprehension improvements
|
||||
- `UP` - pyupgrade automatic Python version upgrades
|
||||
|
||||
### pyproject.toml (MyPy)
|
||||
|
||||
Complete MyPy configuration from cvi-backend-template:
|
||||
|
||||
```toml
|
||||
[tool.mypy]
|
||||
python_version = "3.11"
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
disallow_untyped_defs = true
|
||||
check_untyped_defs = true
|
||||
ignore_missing_imports = false
|
||||
strict_optional = true
|
||||
warn_redundant_casts = true
|
||||
warn_unused_ignores = true
|
||||
warn_no_return = true
|
||||
warn_unreachable = true
|
||||
```
|
||||
|
||||
**Key Settings:**
|
||||
|
||||
- `disallow_untyped_defs: true` - **CRITICAL**: Require type hints on all function definitions
|
||||
- `python_version = "3.11"` - Target Python 3.11+ (projects use 3.12+)
|
||||
- `strict_optional: true` - Strict checking of Optional types
|
||||
- `warn_return_any: true` - Warn when returning Any from typed functions
|
||||
- `warn_unused_configs: true` - Warn about unused mypy config
|
||||
|
||||
**Philosophy:** Type hints are **required** in Grey Haven Python projects. Unlike TypeScript's relaxed rules, Python enforces strict typing.
|
||||
|
||||
### pyproject.toml (Pytest)
|
||||
|
||||
Complete pytest configuration from cvi-backend-template:
|
||||
|
||||
```toml
|
||||
[tool.pytest.ini_options]
|
||||
pythonpath = ["."]
|
||||
asyncio_mode = "auto"
|
||||
testpaths = ["tests"]
|
||||
python_files = ["test_*.py"]
|
||||
python_classes = ["Test*"]
|
||||
python_functions = ["test_*"]
|
||||
markers = [
|
||||
"unit: Unit tests (fast, isolated, mocked)",
|
||||
"integration: Integration tests (database, external services)",
|
||||
"e2e: End-to-end tests (full system)",
|
||||
"benchmark: Performance benchmark tests",
|
||||
"slow: Tests that take > 1 second",
|
||||
]
|
||||
addopts = [
|
||||
"-ra", # Show summary of all test outcomes
|
||||
"--strict-markers", # Require markers to be registered
|
||||
"--strict-config", # Raise on config warnings
|
||||
"--tb=short", # Shorter traceback format
|
||||
]
|
||||
|
||||
[tool.coverage.run]
|
||||
source = ["app"]
|
||||
omit = ["tests/*", "*/migrations/*"]
|
||||
|
||||
[tool.coverage.report]
|
||||
precision = 2
|
||||
show_missing = true
|
||||
skip_covered = false
|
||||
```
|
||||
|
||||
**Marker Explanations:**
|
||||
|
||||
- `@pytest.mark.unit` - Fast, isolated tests with mocked dependencies
|
||||
- `@pytest.mark.integration` - Tests with database or external services
|
||||
- `@pytest.mark.e2e` - Full system end-to-end tests
|
||||
- `@pytest.mark.benchmark` - Performance measurement tests
|
||||
- `@pytest.mark.slow` - Tests taking over 1 second
|
||||
|
||||
**Coverage:** Aim for >80% code coverage across the project.
|
||||
|
||||
## Project Structure Details
|
||||
|
||||
### TypeScript/React Structure (cvi-template)
|
||||
|
||||
```plaintext
|
||||
cvi-template/
|
||||
├── src/
|
||||
│ ├── routes/ # TanStack Router file-based routing
|
||||
│ │ ├── __root.tsx # Root layout
|
||||
│ │ ├── index.tsx # Homepage (/)
|
||||
│ │ ├── _authenticated/ # Protected routes (requires auth)
|
||||
│ │ │ └── dashboard.tsx # /dashboard
|
||||
│ │ └── settings/
|
||||
│ │ ├── index.tsx # /settings
|
||||
│ │ └── profile.tsx # /settings/profile
|
||||
│ │
|
||||
│ ├── lib/
|
||||
│ │ ├── components/ # React components
|
||||
│ │ │ ├── ui/ # Shadcn UI components (button, card, etc.)
|
||||
│ │ │ ├── auth/ # Authentication components
|
||||
│ │ │ └── layout/ # Layout components (header, sidebar)
|
||||
│ │ │
|
||||
│ │ ├── server/ # Server-side code
|
||||
│ │ │ ├── schema/ # Drizzle database schemas (snake_case!)
|
||||
│ │ │ ├── functions/ # TanStack Start server functions
|
||||
│ │ │ ├── auth.ts # Better-auth configuration
|
||||
│ │ │ └── db.ts # Database connection with RLS
|
||||
│ │ │
|
||||
│ │ ├── config/ # Configuration files
|
||||
│ │ │ └── env.ts # Environment validation (@t3-oss/env-core)
|
||||
│ │ │
|
||||
│ │ ├── hooks/ # Custom React hooks (use-* naming)
|
||||
│ │ ├── utils/ # Utility functions
|
||||
│ │ └── types/ # TypeScript type definitions
|
||||
│ │
|
||||
│ ├── clients/ # API client code
|
||||
│ ├── data/ # Zod schemas for data validation
|
||||
│ ├── middleware/ # Route middleware and guards
|
||||
│ ├── services/ # Business logic services
|
||||
│ ├── workers/ # Cloudflare Workers code
|
||||
│ ├── index.css # Global styles (Tailwind)
|
||||
│ └── router.tsx # Router configuration
|
||||
│
|
||||
├── public/ # Static assets
|
||||
├── tests/ # Test files
|
||||
├── .prettierrc # Prettier config
|
||||
├── .eslintrc # ESLint config
|
||||
├── tsconfig.json # TypeScript config
|
||||
├── drizzle.config.ts # Drizzle ORM config
|
||||
├── vite.config.ts # Vite bundler config
|
||||
├── wrangler.jsonc # Cloudflare Workers config
|
||||
└── package.json # Dependencies and scripts
|
||||
```
|
||||
|
||||
### Python/FastAPI Structure (cvi-backend-template)
|
||||
|
||||
```plaintext
|
||||
cvi-backend-template/
|
||||
├── app/
|
||||
│ ├── config/ # Application configuration
|
||||
│ │ └── settings.py # Pydantic settings with env vars
|
||||
│ │
|
||||
│ ├── db/ # Database layer
|
||||
│ │ ├── models/ # SQLModel database models (snake_case!)
|
||||
│ │ │ ├── account.py # Account model
|
||||
│ │ │ ├── tenant.py # Tenant model (multi-tenant)
|
||||
│ │ │ └── user.py # User model
|
||||
│ │ │
|
||||
│ │ ├── repositories/ # Repository pattern (data access)
|
||||
│ │ │ ├── base.py # Base repository
|
||||
│ │ │ ├── account_repository.py
|
||||
│ │ │ ├── tenant_repository.py
|
||||
│ │ │ └── user_repository.py
|
||||
│ │ │
|
||||
│ │ ├── db_types.py # Custom database types (UTCDateTime)
|
||||
│ │ └── session.py # Database session management
|
||||
│ │
|
||||
│ ├── routers/ # FastAPI routers (endpoints)
|
||||
│ │ ├── accounts.py # /accounts endpoints
|
||||
│ │ ├── tenants.py # /tenants endpoints
|
||||
│ │ └── users.py # /users endpoints
|
||||
│ │
|
||||
│ ├── services/ # Business logic layer
|
||||
│ │ ├── auth_service.py # Authentication service
|
||||
│ │ └── billing_service.py # Billing service
|
||||
│ │
|
||||
│ ├── schemas/ # Pydantic schemas (API contracts)
|
||||
│ │ ├── accounts.py # Account request/response schemas
|
||||
│ │ ├── tenants.py # Tenant request/response schemas
|
||||
│ │ └── users.py # User request/response schemas
|
||||
│ │
|
||||
│ ├── utils/ # Utility functions
|
||||
│ │ ├── logging.py # Logging configuration
|
||||
│ │ └── security.py # Security utilities
|
||||
│ │
|
||||
│ ├── dependencies.py # FastAPI dependencies
|
||||
│ └── main.py # FastAPI app entry point
|
||||
│
|
||||
├── tests/ # Test files
|
||||
│ ├── unit/ # Unit tests (@pytest.mark.unit)
|
||||
│ ├── integration/ # Integration tests (@pytest.mark.integration)
|
||||
│ ├── e2e/ # E2E tests (@pytest.mark.e2e)
|
||||
│ ├── conftest.py # Pytest fixtures
|
||||
│ └── __init__.py
|
||||
│
|
||||
├── alembic/ # Database migrations (if using Alembic)
|
||||
├── pyproject.toml # Python project config (Ruff, MyPy, pytest)
|
||||
├── requirements.txt # Python dependencies
|
||||
└── .env.example # Example environment variables
|
||||
```
|
||||
|
||||
## Database Naming Standards
|
||||
|
||||
### Field Naming Rules
|
||||
|
||||
**ALWAYS use snake_case for database columns:**
|
||||
|
||||
```text
|
||||
✅ CORRECT ❌ WRONG
|
||||
created_at createdAt
|
||||
tenant_id tenantId
|
||||
email_address emailAddress
|
||||
is_active isActive
|
||||
first_name firstName
|
||||
last_name lastName
|
||||
phone_number phoneNumber
|
||||
billing_tier billingTier
|
||||
max_retries maxRetries
|
||||
api_key_hash apiKeyHash
|
||||
```
|
||||
|
||||
### Table Naming Rules
|
||||
|
||||
**Use lowercase plural names:**
|
||||
|
||||
```text
|
||||
✅ CORRECT ❌ WRONG
|
||||
users User, Users, user
|
||||
accounts Account, ACCOUNTS
|
||||
tenants Tenant, Tenants
|
||||
organizations Organization
|
||||
subscriptions Subscription
|
||||
```
|
||||
|
||||
### Index Naming Rules
|
||||
|
||||
**Use descriptive index names:**
|
||||
|
||||
```text
|
||||
Format: {table}_{column}_idx
|
||||
|
||||
✅ CORRECT ❌ WRONG
|
||||
user_email_idx idx_1, email_index
|
||||
user_tenant_id_idx tenant_idx
|
||||
organization_slug_idx slug
|
||||
```
|
||||
|
||||
### Foreign Key Naming Rules
|
||||
|
||||
**Reference the parent table:**
|
||||
|
||||
```text
|
||||
Format: {parent_table}_{singular}_id
|
||||
|
||||
✅ CORRECT ❌ WRONG
|
||||
tenant_id tenant
|
||||
account_id acc_id, accountId
|
||||
organization_id org_id, orgId
|
||||
user_id userId, uid
|
||||
```
|
||||
|
||||
## Multi-Tenant Architecture
|
||||
|
||||
### Tenant Isolation Levels
|
||||
|
||||
Grey Haven projects use **row-level** tenant isolation:
|
||||
|
||||
1. **Every table** includes a `tenant_id` or `account_id` field
|
||||
2. **Every query** filters by tenant ID
|
||||
3. **Row Level Security (RLS)** policies enforce tenant boundaries
|
||||
4. **Repository pattern** centralizes tenant filtering
|
||||
|
||||
### RLS Policy Pattern
|
||||
|
||||
Standard RLS policy structure:
|
||||
|
||||
```typescript
|
||||
// TypeScript/Drizzle RLS helper
|
||||
export const inSameTenant = (tenantIdCol: AnyPgColumn, query: SQL): SQL =>
|
||||
sql`${isSameTenant(tenantIdCol)} and (${query})`;
|
||||
|
||||
// Apply to table
|
||||
pgPolicy("table_name_select", {
|
||||
for: "select",
|
||||
to: "public",
|
||||
using: inSameTenant(table.tenantId, sql`true`),
|
||||
});
|
||||
```
|
||||
|
||||
```sql
|
||||
-- SQL RLS Policy
|
||||
CREATE POLICY tenant_isolation_policy ON users
|
||||
USING (tenant_id = current_setting('app.tenant_id')::text);
|
||||
```
|
||||
|
||||
### Repository Tenant Filtering
|
||||
|
||||
Every repository method filters by tenant:
|
||||
|
||||
```python
|
||||
# Python repository pattern
|
||||
async def get_by_id(self, id: UUID, tenant_id: UUID) -> Optional[Model]:
|
||||
"""Get record by ID with tenant isolation."""
|
||||
result = await self.session.execute(
|
||||
select(self.model)
|
||||
.where(self.model.id == id)
|
||||
.where(self.model.tenant_id == tenant_id) # Tenant filter!
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
```
|
||||
|
||||
## Import Organization
|
||||
|
||||
### TypeScript Import Order
|
||||
|
||||
Auto-organized by `prettier-plugin-organize-imports`:
|
||||
|
||||
```typescript
|
||||
// 1. External libraries
|
||||
import { useState, useEffect } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
// 2. Internal modules (path alias)
|
||||
import { Button } from "~/lib/components/ui/button";
|
||||
import { useAuth } from "~/lib/hooks/use-auth";
|
||||
import { env } from "~/lib/config/env";
|
||||
|
||||
// 3. Relative imports
|
||||
import { helpers } from "./helpers";
|
||||
```
|
||||
|
||||
### Python Import Order
|
||||
|
||||
Auto-organized by Ruff `isort`:
|
||||
|
||||
```python
|
||||
# 1. Standard library imports
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
# 2. Third-party imports
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlmodel import select
|
||||
from pydantic import BaseModel
|
||||
|
||||
# 3. Local imports (app.*)
|
||||
from app.db.models.user import User
|
||||
from app.db.repositories.user_repository import UserRepository
|
||||
from app.schemas.user import UserCreate, UserResponse
|
||||
```
|
||||
|
||||
## Line Length Reasoning
|
||||
|
||||
### TypeScript: 90 Characters
|
||||
|
||||
**Why 90?**
|
||||
|
||||
- Comfortable reading width on modern displays
|
||||
- Allows two editor panes side-by-side
|
||||
- Balances readability with code density
|
||||
- TailwindCSS classes can be long - 90 gives room
|
||||
- Standard 80 is too restrictive for modern development
|
||||
|
||||
### Python: 130 Characters
|
||||
|
||||
**Why 130?**
|
||||
|
||||
- Type hints can be verbose: `Annotated[AccountRepository, Depends(get_account_repository)]`
|
||||
- Pydantic field definitions: `Field(default=None, description="Long description here")`
|
||||
- Allows descriptive variable names without constant wrapping
|
||||
- FastAPI decorators are long: `@router.post("/endpoint", response_model=ResponseSchema)`
|
||||
- PEP 8's 79 is outdated for modern development
|
||||
|
||||
## Pre-commit Hooks
|
||||
|
||||
Both templates use pre-commit hooks for code quality:
|
||||
|
||||
### TypeScript Pre-commit
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
# Run linting
|
||||
npm run lint
|
||||
|
||||
# Run formatting check
|
||||
npm run format:check
|
||||
|
||||
# Run type checking
|
||||
npx tsc --noEmit
|
||||
```
|
||||
|
||||
### Python Pre-commit
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Activate virtual environment
|
||||
source .venv/bin/activate
|
||||
|
||||
# Run Ruff formatter
|
||||
ruff format .
|
||||
|
||||
# Run Ruff linter
|
||||
ruff check --fix .
|
||||
|
||||
# Run MyPy type checking
|
||||
mypy app/
|
||||
|
||||
# Run tests
|
||||
pytest -m "not slow"
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- **Frontend Template**: [cvi-template](https://github.com/greyhaven-ai/cvi-template) - TanStack Start + React 19 + Drizzle
|
||||
- **Backend Template**: [cvi-backend-template](https://github.com/greyhaven-ai/cvi-backend-template) - FastAPI + SQLModel + PostgreSQL
|
||||
- **Prettier Docs**: https://prettier.io/docs/en/configuration.html
|
||||
- **ESLint Docs**: https://eslint.org/docs/latest/use/configure/
|
||||
- **Ruff Docs**: https://docs.astral.sh/ruff/
|
||||
- **Drizzle ORM**: https://orm.drizzle.team/docs/overview
|
||||
- **TanStack Start**: https://tanstack.com/router/latest/docs/framework/react/start/overview
|
||||
199
skills/code-style/SKILL.md
Normal file
199
skills/code-style/SKILL.md
Normal file
@@ -0,0 +1,199 @@
|
||||
---
|
||||
name: grey-haven-code-style
|
||||
description: "Apply Grey Haven Studio's TypeScript/React and Python/FastAPI coding standards from production templates. Use when writing code, reviewing PRs, fixing linting errors, formatting files, or when the user mentions 'code standards', 'Grey Haven style', 'linting', 'Prettier', 'ESLint', 'Ruff', 'formatting rules', or 'coding conventions'. Includes exact Prettier/ESLint/Ruff configs, naming conventions, project structure, and multi-tenant database patterns."
|
||||
---
|
||||
|
||||
# Grey Haven Code Style Standards
|
||||
|
||||
**Actual coding standards from Grey Haven Studio production templates.**
|
||||
|
||||
Follow these exactly when working on Grey Haven codebases. This skill provides navigation to detailed examples, reference configs, and templates.
|
||||
|
||||
## Supporting Documentation
|
||||
|
||||
- **[EXAMPLES.md](EXAMPLES.md)** - Copy-paste code examples for TypeScript and Python
|
||||
- **[REFERENCE.md](REFERENCE.md)** - Complete config files and detailed rule explanations
|
||||
- **[templates/](templates/)** - Ready-to-use starter files
|
||||
- **[checklists/](checklists/)** - Code review checklists
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### TypeScript/React (Frontend)
|
||||
|
||||
Based on `cvi-template` - TanStack Start + React 19
|
||||
|
||||
**Key Settings:**
|
||||
|
||||
- **Line width:** 90 characters
|
||||
- **Tab width:** 2 spaces
|
||||
- **Quotes:** Double quotes
|
||||
- **Semicolons:** Required
|
||||
- **Trailing commas:** Always
|
||||
- **ESLint:** Pragmatic (allows `any`, unused vars)
|
||||
- **Path alias:** `~/` maps to `./src/*`
|
||||
|
||||
**Naming Conventions:**
|
||||
|
||||
- Variables/Functions: `camelCase` (`getUserData`, `isAuthenticated`)
|
||||
- Components: `PascalCase` (`UserProfile`, `AuthProvider`)
|
||||
- Constants: `UPPER_SNAKE_CASE` (`API_BASE_URL`, `MAX_RETRIES`)
|
||||
- Types/Interfaces: `PascalCase` (`User`, `AuthConfig`)
|
||||
- **Database fields:** `snake_case` (`user_id`, `created_at`, `tenant_id`) ⚠️ CRITICAL
|
||||
|
||||
**Project Structure:**
|
||||
|
||||
```plaintext
|
||||
src/
|
||||
├── routes/ # File-based routing (TanStack Router)
|
||||
├── lib/
|
||||
│ ├── components/ # UI components (grouped by feature)
|
||||
│ ├── server/ # Server functions and DB schema
|
||||
│ ├── config/ # Environment validation
|
||||
│ ├── hooks/ # Custom React hooks (use-* naming)
|
||||
│ ├── utils/ # Utility functions
|
||||
│ └── types/ # TypeScript definitions
|
||||
└── public/ # Static assets
|
||||
```
|
||||
|
||||
### Python/FastAPI (Backend)
|
||||
|
||||
Based on `cvi-backend-template` - FastAPI + SQLModel
|
||||
|
||||
**Key Settings:**
|
||||
|
||||
- **Line length:** 130 characters
|
||||
- **Indent:** 4 spaces
|
||||
- **Type hints:** Required on all functions
|
||||
- **Auto-fix:** Ruff fixes issues automatically
|
||||
|
||||
**Naming Conventions:**
|
||||
|
||||
- Functions/Variables: `snake_case` (`get_user_data`, `is_authenticated`)
|
||||
- Classes: `PascalCase` (`UserRepository`, `AuthService`)
|
||||
- Constants: `UPPER_SNAKE_CASE` (`API_BASE_URL`, `MAX_RETRIES`)
|
||||
- **Database fields:** `snake_case` (`user_id`, `created_at`, `tenant_id`) ⚠️ CRITICAL
|
||||
- Boolean fields: Prefix with `is_` or `has_` (`is_active`, `has_access`)
|
||||
|
||||
**Project Structure:**
|
||||
|
||||
```plaintext
|
||||
app/
|
||||
├── config/ # Application settings
|
||||
├── db/
|
||||
│ ├── models/ # SQLModel entities
|
||||
│ └── repositories/ # Repository pattern (tenant isolation)
|
||||
├── routers/ # FastAPI endpoints
|
||||
├── services/ # Business logic
|
||||
├── schemas/ # Pydantic models (API contracts)
|
||||
└── utils/ # Utilities
|
||||
```
|
||||
|
||||
## Database Field Convention (CRITICAL)
|
||||
|
||||
**ALWAYS use `snake_case` for database column names** - this is non-negotiable in Grey Haven projects.
|
||||
|
||||
✅ **Correct:**
|
||||
|
||||
```typescript
|
||||
// TypeScript - Drizzle schema
|
||||
export const users = pgTable("users", {
|
||||
id: uuid("id").primaryKey(),
|
||||
created_at: timestamp("created_at").defaultNow(),
|
||||
tenant_id: uuid("tenant_id").notNull(),
|
||||
email_address: text("email_address").notNull(),
|
||||
is_active: boolean("is_active").default(true),
|
||||
});
|
||||
```
|
||||
|
||||
```python
|
||||
# Python - SQLModel
|
||||
class User(SQLModel, table=True):
|
||||
id: UUID = Field(default_factory=uuid4, primary_key=True)
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
tenant_id: UUID = Field(foreign_key="tenants.id", index=True)
|
||||
email_address: str = Field(unique=True, index=True)
|
||||
is_active: bool = Field(default=True)
|
||||
```
|
||||
|
||||
❌ **Wrong:**
|
||||
|
||||
```typescript
|
||||
// DON'T use camelCase in database schemas
|
||||
export const users = pgTable("users", {
|
||||
id: uuid("id"),
|
||||
createdAt: timestamp("createdAt"), // WRONG!
|
||||
tenantId: uuid("tenantId"), // WRONG!
|
||||
emailAddress: text("emailAddress"), // WRONG!
|
||||
});
|
||||
```
|
||||
|
||||
**See [EXAMPLES.md](EXAMPLES.md#database-schemas) for complete examples.**
|
||||
|
||||
## Multi-Tenant Architecture
|
||||
|
||||
**Every database table must include tenant isolation:**
|
||||
|
||||
- **Field name:** `tenant_id` (snake_case in DB) or `tenantId` (camelCase in TypeScript code)
|
||||
- **Type:** UUID foreign key to tenants table
|
||||
- **Index:** Always indexed for query performance
|
||||
- **RLS:** Use Row Level Security policies for tenant isolation
|
||||
- **Repository pattern:** All queries filter by `tenant_id`
|
||||
|
||||
**See [EXAMPLES.md](EXAMPLES.md#multi-tenant-patterns) for implementation patterns.**
|
||||
|
||||
## Virtual Environment (Python Projects)
|
||||
|
||||
**⚠️ ALWAYS activate virtual environment before running Python commands:**
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
Required for:
|
||||
|
||||
- Running tests (`pytest`)
|
||||
- Running pre-commit hooks
|
||||
- Using task commands (`task test`, `task format`)
|
||||
- Any Python script execution
|
||||
|
||||
## When to Apply This Skill
|
||||
|
||||
Use this skill when:
|
||||
|
||||
- ✅ Writing new TypeScript/React or Python/FastAPI code
|
||||
- ✅ Reviewing code in pull requests
|
||||
- ✅ Fixing linting or formatting errors
|
||||
- ✅ Setting up new projects from templates
|
||||
- ✅ Configuring Prettier, ESLint, or Ruff
|
||||
- ✅ Creating database schemas
|
||||
- ✅ Implementing multi-tenant features
|
||||
- ✅ User mentions: "code standards", "linting rules", "Grey Haven style", "formatting"
|
||||
|
||||
## Template References
|
||||
|
||||
These standards come from actual Grey Haven production templates:
|
||||
|
||||
- **Frontend:** `cvi-template` (TanStack Start + React 19 + Drizzle)
|
||||
- **Backend:** `cvi-backend-template` (FastAPI + SQLModel + PostgreSQL)
|
||||
|
||||
When in doubt, reference these templates for patterns and configurations.
|
||||
|
||||
## Critical Reminders
|
||||
|
||||
1. **Line lengths:** TypeScript=90, Python=130 (NOT 80/88)
|
||||
2. **Database fields:** ALWAYS `snake_case` (both TypeScript and Python schemas)
|
||||
3. **`any` type:** ALLOWED in Grey Haven TypeScript (pragmatic approach)
|
||||
4. **Double quotes:** TypeScript uses double quotes (`singleQuote: false`)
|
||||
5. **Type hints:** REQUIRED in Python (`disallow_untyped_defs: true`)
|
||||
6. **Virtual env:** MUST activate before Python commands
|
||||
7. **Multi-tenant:** Every table has `tenant_id`/`tenantId`
|
||||
8. **Path aliases:** Use `~/` for TypeScript imports from `src/`
|
||||
9. **Trailing commas:** ALWAYS in TypeScript (`trailingComma: "all"`)
|
||||
10. **Pre-commit hooks:** Run before every commit (both projects)
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **Need examples?** See [EXAMPLES.md](EXAMPLES.md) for copy-paste code
|
||||
- **Need configs?** See [REFERENCE.md](REFERENCE.md) for complete config files
|
||||
- **Need templates?** See [templates/](templates/) for starter files
|
||||
- **Reviewing code?** Use [checklists/](checklists/) for systematic reviews
|
||||
141
skills/code-style/checklists/python-review.md
Normal file
141
skills/code-style/checklists/python-review.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# Python/FastAPI Code Review Checklist
|
||||
|
||||
Use this checklist when reviewing Python/FastAPI code or creating pull requests for Grey Haven projects.
|
||||
|
||||
## Formatting & Style
|
||||
|
||||
- [ ] **Line length**: Code lines do not exceed 130 characters (CRITICAL!)
|
||||
- [ ] **Indentation**: Uses 4 spaces (not tabs or 2 spaces)
|
||||
- [ ] **Quotes**: Uses double quotes for strings (Ruff default)
|
||||
- [ ] **Line endings**: Uses Unix-style line endings (\\n)
|
||||
- [ ] **Ruff formatted**: Code is formatted with Ruff (`ruff format .`)
|
||||
- [ ] **Ruff linting**: No Ruff linting errors (`ruff check .`)
|
||||
|
||||
## Type Hints
|
||||
|
||||
- [ ] **Function signatures**: ALL functions have type hints (CRITICAL!)
|
||||
- Parameters: `def get_user(user_id: str) -> Optional[User]:`
|
||||
- Return types: Always include return type annotation
|
||||
- [ ] **MyPy passing**: `mypy app/` has no type errors
|
||||
- [ ] **Optional types**: Uses `Optional[T]` or `T | None` for nullable values
|
||||
- [ ] **Generic types**: Uses proper generic types (`list[str]`, `dict[str, Any]`)
|
||||
- [ ] **Type imports**: Imports types from `typing` module
|
||||
|
||||
## Database Models (SQLModel)
|
||||
|
||||
- [ ] **snake_case fields**: ALL database column names use snake_case (CRITICAL!)
|
||||
- ✅ `created_at`, `tenant_id`, `email_address`, `is_active`
|
||||
- ❌ `createdAt`, `tenantId`, `emailAddress`, `isActive`
|
||||
- [ ] **Multi-tenant**: Models include `tenant_id` field
|
||||
- [ ] **Field descriptions**: All fields have `description` parameter
|
||||
- [ ] **Indexes**: Frequently queried fields have `index=True`
|
||||
- [ ] **Constraints**: Foreign keys, unique constraints properly defined
|
||||
- [ ] **Timestamps**: Uses UTC datetime (`UTCDateTime` type if available)
|
||||
- [ ] **Table names**: Uses `__tablename__` with lowercase plural names
|
||||
|
||||
## Pydantic Schemas
|
||||
|
||||
- [ ] **Schema hierarchy**: Follows Base/Create/Update/Response pattern
|
||||
- [ ] **Validators**: Uses `@field_validator` for custom validation
|
||||
- [ ] **ConfigDict**: Response schemas have `model_config = ConfigDict(from_attributes=True)`
|
||||
- [ ] **Field constraints**: Uses appropriate constraints (`ge`, `le`, `max_length`, etc.)
|
||||
- [ ] **Optional fields**: Update schemas use `| None` for optional fields
|
||||
- [ ] **Descriptions**: All fields have descriptions in `Field(..., description="...")`
|
||||
|
||||
## FastAPI Endpoints
|
||||
|
||||
- [ ] **Type hints**: ALL endpoint functions fully typed with `Annotated`
|
||||
- [ ] **Docstrings**: Endpoints have comprehensive docstrings (Args, Returns, Raises)
|
||||
- [ ] **Status codes**: Uses appropriate HTTP status codes from `status` module
|
||||
- [ ] **Response models**: Endpoints specify `response_model`
|
||||
- [ ] **Dependencies**: Uses Depends() for repository and auth dependencies
|
||||
- [ ] **Error handling**: Raises HTTPException with proper status codes and messages
|
||||
- [ ] **Router prefix**: Router has appropriate prefix and tags
|
||||
|
||||
## Repository Pattern
|
||||
|
||||
- [ ] **Tenant isolation**: ALL queries filter by `tenant_id` (CRITICAL!)
|
||||
- [ ] **Type hints**: Repository methods fully typed
|
||||
- [ ] **Async/await**: Uses async/await for database operations
|
||||
- [ ] **Session management**: Properly commits and refreshes after changes
|
||||
- [ ] **Error handling**: Handles database errors appropriately
|
||||
- [ ] **CRUD methods**: Implements standard create, read, update, delete methods
|
||||
|
||||
## Multi-Tenant Architecture
|
||||
|
||||
- [ ] **Tenant filtering**: All queries include tenant_id filter
|
||||
- [ ] **Repository methods**: Accept `tenant_id` parameter
|
||||
- [ ] **Validation**: Validates user has access to requested tenant
|
||||
- [ ] **Isolation**: No cross-tenant data leakage possible
|
||||
- [ ] **Foreign keys**: Multi-tenant relationships properly enforced
|
||||
|
||||
## Imports Organization
|
||||
|
||||
- [ ] **Import order**: Follows Ruff isort rules:
|
||||
1. Standard library imports
|
||||
2. Third-party imports
|
||||
3. Local imports (app.*)
|
||||
- [ ] **Absolute imports**: Uses absolute imports (not relative)
|
||||
- [ ] **Grouped imports**: Related imports grouped together
|
||||
- [ ] **No unused imports**: All imports are used
|
||||
|
||||
## Testing (Pytest)
|
||||
|
||||
- [ ] **Tests exist**: Endpoints/functions have corresponding tests
|
||||
- [ ] **Test markers**: Uses pytest markers (@pytest.mark.unit, @pytest.mark.integration)
|
||||
- [ ] **Fixtures**: Uses pytest fixtures for setup
|
||||
- [ ] **Async tests**: Async tests decorated with `@pytest.mark.asyncio`
|
||||
- [ ] **Mocking**: Uses AsyncMock/MagicMock for external dependencies
|
||||
- [ ] **Coverage**: Maintains or improves test coverage (aim for >80%)
|
||||
- [ ] **Assertions**: Tests have clear, specific assertions
|
||||
|
||||
## Security
|
||||
|
||||
- [ ] **Input validation**: Uses Pydantic schemas for input validation
|
||||
- [ ] **SQL injection**: Uses parameterized queries (SQLModel handles this)
|
||||
- [ ] **Authentication**: Endpoints require authentication via dependencies
|
||||
- [ ] **Authorization**: Validates user has permission for actions
|
||||
- [ ] **Secrets**: Uses environment variables for secrets (never hardcode)
|
||||
- [ ] **Rate limiting**: Critical endpoints have rate limiting
|
||||
|
||||
## Error Handling
|
||||
|
||||
- [ ] **HTTPException**: Raises HTTPException with proper status codes
|
||||
- [ ] **Error messages**: Error messages are descriptive and user-friendly
|
||||
- [ ] **Logging**: Errors are logged appropriately
|
||||
- [ ] **Validation errors**: Pydantic validation errors return 422
|
||||
- [ ] **Not found**: Returns 404 for missing resources
|
||||
|
||||
## Performance
|
||||
|
||||
- [ ] **Database queries**: Queries are efficient (no N+1 problems)
|
||||
- [ ] **Indexes**: Frequently filtered columns have indexes
|
||||
- [ ] **Pagination**: List endpoints implement pagination (limit/offset)
|
||||
- [ ] **Async operations**: Uses async/await for I/O operations
|
||||
- [ ] **Connection pooling**: Database uses connection pooling
|
||||
|
||||
## Documentation
|
||||
|
||||
- [ ] **Docstrings**: All functions have comprehensive docstrings
|
||||
- [ ] **OpenAPI docs**: FastAPI auto-docs are accurate and complete
|
||||
- [ ] **Type annotations**: Type hints serve as documentation
|
||||
- [ ] **README updated**: README reflects any new features/changes
|
||||
- [ ] **API examples**: Complex endpoints have usage examples
|
||||
|
||||
## Pre-commit Checks
|
||||
|
||||
- [ ] **Virtual env active**: Ran commands with `source .venv/bin/activate`
|
||||
- [ ] **Ruff formatting**: `ruff format .` applied
|
||||
- [ ] **Ruff linting**: `ruff check --fix .` passing
|
||||
- [ ] **MyPy**: `mypy app/` passing with no type errors
|
||||
- [ ] **Tests passing**: `pytest` passes all tests
|
||||
- [ ] **Coverage**: Test coverage meets threshold (>80%)
|
||||
- [ ] **Pre-commit hooks**: All pre-commit hooks pass
|
||||
|
||||
## API Design
|
||||
|
||||
- [ ] **RESTful**: Endpoints follow REST principles
|
||||
- [ ] **Naming**: Endpoints use clear, descriptive names
|
||||
- [ ] **Versioning**: API versioned appropriately (`/v1/`)
|
||||
- [ ] **Consistency**: Similar endpoints have consistent patterns
|
||||
- [ ] **CRUD complete**: Resource has full CRUD operations if needed
|
||||
113
skills/code-style/checklists/typescript-review.md
Normal file
113
skills/code-style/checklists/typescript-review.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# TypeScript/React Code Review Checklist
|
||||
|
||||
Use this checklist when reviewing TypeScript/React code or creating pull requests for Grey Haven projects.
|
||||
|
||||
## Formatting & Style
|
||||
|
||||
- [ ] **Line width**: Code lines do not exceed 90 characters
|
||||
- [ ] **Indentation**: Uses 2 spaces (not tabs or 4 spaces)
|
||||
- [ ] **Semicolons**: All statements end with semicolons
|
||||
- [ ] **Quotes**: Uses double quotes for strings (not single quotes)
|
||||
- [ ] **Trailing commas**: Has trailing commas in objects, arrays, function parameters
|
||||
- [ ] **Prettier formatted**: Code is formatted with Prettier (`npm run format`)
|
||||
- [ ] **ESLint passing**: No ESLint errors (`npm run lint`)
|
||||
|
||||
## TypeScript
|
||||
|
||||
- [ ] **Type safety**: No `any` types used unnecessarily (but allowed when appropriate)
|
||||
- [ ] **Type annotations**: Complex types have proper interfaces/types defined
|
||||
- [ ] **Imports organized**: Imports are auto-sorted (external → internal → relative)
|
||||
- [ ] **Path aliases**: Uses `~/` path alias for src imports (not `../..`)
|
||||
- [ ] **tsconfig compliance**: Follows strict TypeScript configuration
|
||||
|
||||
## Database Schema (Drizzle)
|
||||
|
||||
- [ ] **snake_case fields**: ALL database column names use snake_case (CRITICAL!)
|
||||
- ✅ `created_at`, `tenant_id`, `email_address`
|
||||
- ❌ `createdAt`, `tenantId`, `emailAddress`
|
||||
- [ ] **Multi-tenant**: Tables include `tenant_id` or `tenantId` field
|
||||
- [ ] **Indexes**: Frequently queried fields have indexes
|
||||
- [ ] **RLS policies**: Row Level Security policies defined for multi-tenant isolation
|
||||
- [ ] **Foreign keys**: Relationships use proper foreign key constraints
|
||||
|
||||
## React Components
|
||||
|
||||
- [ ] **Component structure**: Follows Grey Haven component pattern:
|
||||
1. Imports (auto-sorted)
|
||||
2. Types/Interfaces
|
||||
3. Component definition
|
||||
4. State management (hooks first)
|
||||
5. Queries/Mutations
|
||||
6. Effects
|
||||
7. Event handlers
|
||||
8. Conditional renders
|
||||
9. Main render
|
||||
- [ ] **Naming**: Components use PascalCase (`UserProfile.tsx`)
|
||||
- [ ] **Props typed**: Component props have TypeScript interfaces
|
||||
- [ ] **Hooks named**: Custom hooks start with `use-` prefix
|
||||
- [ ] **Default export**: Route components export default
|
||||
|
||||
## TanStack Query
|
||||
|
||||
- [ ] **Query keys**: Uses descriptive, unique query keys
|
||||
- [ ] **staleTime**: Sets appropriate staleTime (default: 60000ms / 1 minute)
|
||||
- [ ] **Error handling**: Handles loading and error states
|
||||
- [ ] **Mutations**: Uses useMutation for data updates
|
||||
- [ ] **Invalidation**: Invalidates queries after mutations
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- [ ] **Validation**: Environment variables validated with @t3-oss/env-core and Zod
|
||||
- [ ] **VITE_ prefix**: Client variables prefixed with `VITE_`
|
||||
- [ ] **Types**: All env variables have proper Zod schemas
|
||||
- [ ] **Documentation**: Env variables have JSDoc comments
|
||||
|
||||
## Multi-Tenant Architecture
|
||||
|
||||
- [ ] **Tenant isolation**: All queries filter by `tenant_id` / `tenantId`
|
||||
- [ ] **RLS enabled**: Row Level Security policies enforce tenant boundaries
|
||||
- [ ] **Context usage**: Uses tenant context from auth provider
|
||||
- [ ] **API calls**: Includes tenant ID in API requests
|
||||
|
||||
## Testing
|
||||
|
||||
- [ ] **Tests exist**: Components/functions have corresponding tests
|
||||
- [ ] **Coverage**: Maintains or improves test coverage (aim for >80%)
|
||||
- [ ] **Test structure**: Tests follow Arrange-Act-Assert pattern
|
||||
- [ ] **Mocking**: External dependencies are properly mocked
|
||||
|
||||
## Security
|
||||
|
||||
- [ ] **Input validation**: User inputs are validated (Zod schemas)
|
||||
- [ ] **XSS prevention**: No dangerouslySetInnerHTML without sanitization
|
||||
- [ ] **API security**: API endpoints require authentication
|
||||
- [ ] **Secrets**: No secrets or API keys in code (use env variables)
|
||||
|
||||
## Accessibility
|
||||
|
||||
- [ ] **Semantic HTML**: Uses appropriate HTML elements
|
||||
- [ ] **ARIA labels**: Interactive elements have accessible labels
|
||||
- [ ] **Keyboard nav**: Interactive elements are keyboard accessible
|
||||
- [ ] **Color contrast**: Text has sufficient color contrast
|
||||
|
||||
## Performance
|
||||
|
||||
- [ ] **Code splitting**: Large components use lazy loading if appropriate
|
||||
- [ ] **Memoization**: Expensive calculations use useMemo/useCallback
|
||||
- [ ] **Query optimization**: Database queries are efficient (no N+1)
|
||||
- [ ] **Bundle size**: No unnecessary dependencies added
|
||||
|
||||
## Documentation
|
||||
|
||||
- [ ] **JSDoc comments**: Complex functions have JSDoc comments
|
||||
- [ ] **README updated**: README reflects any new features/changes
|
||||
- [ ] **Type exports**: Exported types are documented
|
||||
- [ ] **Examples**: Complex patterns have usage examples
|
||||
|
||||
## Pre-commit Checks
|
||||
|
||||
- [ ] **Build passes**: `npm run build` completes without errors
|
||||
- [ ] **Linting passes**: `npm run lint` has no errors
|
||||
- [ ] **Type checking**: `npx tsc --noEmit` has no errors
|
||||
- [ ] **Tests passing**: `npm test` passes all tests
|
||||
- [ ] **Pre-commit hooks**: Husky pre-commit hooks all pass
|
||||
18
skills/code-style/templates/.eslintrc.json
Normal file
18
skills/code-style/templates/.eslintrc.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": { "browser": true, "es2020": true },
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react-hooks/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["react-refresh"],
|
||||
"rules": {
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"no-unused-vars": "off",
|
||||
"react-refresh/only-export-components": "off"
|
||||
}
|
||||
}
|
||||
9
skills/code-style/templates/.prettierrc.json
Normal file
9
skills/code-style/templates/.prettierrc.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"printWidth": 90,
|
||||
"singleQuote": false,
|
||||
"endOfLine": "lf",
|
||||
"trailingComma": "all",
|
||||
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"]
|
||||
}
|
||||
186
skills/code-style/templates/python-endpoint.py
Normal file
186
skills/code-style/templates/python-endpoint.py
Normal file
@@ -0,0 +1,186 @@
|
||||
"""Example FastAPI Router Template.
|
||||
|
||||
Copy and adapt this for new Grey Haven API endpoints.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Annotated
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
# Replace with your actual imports
|
||||
from app.db.models.example import ExampleDB
|
||||
from app.db.repositories.example_repository import ExampleRepository
|
||||
from app.dependencies import get_current_user, get_example_repository
|
||||
from app.schemas.example import ExampleCreate, ExampleResponse, ExampleUpdate
|
||||
|
||||
# 1. Create router with tags and dependencies
|
||||
router = APIRouter(
|
||||
prefix="/examples",
|
||||
tags=["examples"],
|
||||
dependencies=[Depends(get_current_user)], # Require authentication
|
||||
)
|
||||
|
||||
|
||||
# 2. POST endpoint - Create resource
|
||||
@router.post("/", response_model=ExampleResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_example(
|
||||
data: ExampleCreate,
|
||||
repo: Annotated[ExampleRepository, Depends(get_example_repository)],
|
||||
current_user: Annotated[dict, Depends(get_current_user)],
|
||||
) -> ExampleResponse:
|
||||
"""
|
||||
Create a new example resource.
|
||||
|
||||
Args:
|
||||
data: Example creation data
|
||||
repo: Example repository dependency
|
||||
current_user: Currently authenticated user
|
||||
|
||||
Returns:
|
||||
ExampleResponse: Created example
|
||||
|
||||
Raises:
|
||||
HTTPException: If creation fails
|
||||
"""
|
||||
# Create resource with tenant isolation
|
||||
example = await repo.create(data, tenant_id=current_user["tenant_id"])
|
||||
return ExampleResponse.model_validate(example)
|
||||
|
||||
|
||||
# 3. GET endpoint - Retrieve single resource
|
||||
@router.get("/{example_id}", response_model=ExampleResponse)
|
||||
async def get_example(
|
||||
example_id: UUID,
|
||||
repo: Annotated[ExampleRepository, Depends(get_example_repository)],
|
||||
current_user: Annotated[dict, Depends(get_current_user)],
|
||||
) -> ExampleResponse:
|
||||
"""
|
||||
Get example by ID with tenant isolation.
|
||||
|
||||
Args:
|
||||
example_id: Example UUID
|
||||
repo: Example repository dependency
|
||||
current_user: Currently authenticated user
|
||||
|
||||
Returns:
|
||||
ExampleResponse: Example data
|
||||
|
||||
Raises:
|
||||
HTTPException: If not found
|
||||
"""
|
||||
# Get with tenant isolation
|
||||
example = await repo.get_by_id(example_id, tenant_id=current_user["tenant_id"])
|
||||
|
||||
if not example:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Example not found"
|
||||
)
|
||||
|
||||
return ExampleResponse.model_validate(example)
|
||||
|
||||
|
||||
# 4. GET endpoint - List resources with pagination
|
||||
@router.get("/", response_model=list[ExampleResponse])
|
||||
async def list_examples(
|
||||
repo: Annotated[ExampleRepository, Depends(get_example_repository)],
|
||||
current_user: Annotated[dict, Depends(get_current_user)],
|
||||
limit: int = Query(100, ge=1, le=1000, description="Max items to return"),
|
||||
offset: int = Query(0, ge=0, description="Number of items to skip"),
|
||||
is_active: bool | None = Query(None, description="Filter by active status"),
|
||||
) -> list[ExampleResponse]:
|
||||
"""
|
||||
List examples with pagination and filtering.
|
||||
|
||||
Args:
|
||||
repo: Example repository dependency
|
||||
current_user: Currently authenticated user
|
||||
limit: Maximum number of items to return
|
||||
offset: Number of items to skip
|
||||
is_active: Optional filter by active status
|
||||
|
||||
Returns:
|
||||
list[ExampleResponse]: List of examples
|
||||
"""
|
||||
# List with tenant isolation
|
||||
examples = await repo.list_by_tenant(
|
||||
tenant_id=current_user["tenant_id"],
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
is_active=is_active,
|
||||
)
|
||||
|
||||
return [ExampleResponse.model_validate(e) for e in examples]
|
||||
|
||||
|
||||
# 5. PATCH endpoint - Update resource
|
||||
@router.patch("/{example_id}", response_model=ExampleResponse)
|
||||
async def update_example(
|
||||
example_id: UUID,
|
||||
data: ExampleUpdate,
|
||||
repo: Annotated[ExampleRepository, Depends(get_example_repository)],
|
||||
current_user: Annotated[dict, Depends(get_current_user)],
|
||||
) -> ExampleResponse:
|
||||
"""
|
||||
Update example by ID with tenant isolation.
|
||||
|
||||
Args:
|
||||
example_id: Example UUID
|
||||
data: Update data (partial fields)
|
||||
repo: Example repository dependency
|
||||
current_user: Currently authenticated user
|
||||
|
||||
Returns:
|
||||
ExampleResponse: Updated example
|
||||
|
||||
Raises:
|
||||
HTTPException: If not found
|
||||
"""
|
||||
# Get existing example with tenant isolation
|
||||
example = await repo.get_by_id(example_id, tenant_id=current_user["tenant_id"])
|
||||
|
||||
if not example:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Example not found"
|
||||
)
|
||||
|
||||
# Update fields (exclude_unset to only update provided fields)
|
||||
update_dict = data.model_dump(exclude_unset=True)
|
||||
for field, value in update_dict.items():
|
||||
setattr(example, field, value)
|
||||
|
||||
# Save updates
|
||||
updated = await repo.update(example)
|
||||
return ExampleResponse.model_validate(updated)
|
||||
|
||||
|
||||
# 6. DELETE endpoint - Delete resource
|
||||
@router.delete("/{example_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_example(
|
||||
example_id: UUID,
|
||||
repo: Annotated[ExampleRepository, Depends(get_example_repository)],
|
||||
current_user: Annotated[dict, Depends(get_current_user)],
|
||||
) -> None:
|
||||
"""
|
||||
Delete example by ID with tenant isolation.
|
||||
|
||||
Args:
|
||||
example_id: Example UUID
|
||||
repo: Example repository dependency
|
||||
current_user: Currently authenticated user
|
||||
|
||||
Raises:
|
||||
HTTPException: If not found
|
||||
"""
|
||||
# Get existing example with tenant isolation
|
||||
example = await repo.get_by_id(example_id, tenant_id=current_user["tenant_id"])
|
||||
|
||||
if not example:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Example not found"
|
||||
)
|
||||
|
||||
# Delete
|
||||
await repo.delete(example_id, tenant_id=current_user["tenant_id"])
|
||||
126
skills/code-style/templates/python-model.py
Normal file
126
skills/code-style/templates/python-model.py
Normal file
@@ -0,0 +1,126 @@
|
||||
"""Example SQLModel Database Model Template.
|
||||
|
||||
Copy and adapt this for new Grey Haven database models.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from sqlalchemy import Column as SAColumn
|
||||
from sqlmodel import JSON, Column, Field, SQLModel
|
||||
|
||||
|
||||
def utc_now() -> datetime:
|
||||
"""Return current UTC datetime."""
|
||||
from datetime import UTC
|
||||
|
||||
return datetime.now(UTC)
|
||||
|
||||
|
||||
class ExampleDB(SQLModel, table=True): # type: ignore[call-arg]
|
||||
"""
|
||||
Example database model with multi-tenant support.
|
||||
|
||||
This model demonstrates Grey Haven's database conventions:
|
||||
- snake_case field names
|
||||
- Multi-tenant isolation with tenant_id
|
||||
- UTC timestamps
|
||||
- Proper indexes
|
||||
- Comprehensive docstrings
|
||||
"""
|
||||
|
||||
__tablename__ = "examples"
|
||||
|
||||
# Primary identification
|
||||
id: UUID = Field(
|
||||
default_factory=uuid4, primary_key=True, description="Unique example identifier"
|
||||
)
|
||||
|
||||
# Multi-tenant field (CRITICAL - always include!)
|
||||
tenant_id: UUID = Field(
|
||||
foreign_key="tenants.id", index=True, description="Owning tenant identifier"
|
||||
)
|
||||
|
||||
# Example fields - all snake_case!
|
||||
name: str = Field(index=True, max_length=255, description="Example name")
|
||||
description: str | None = Field(
|
||||
default=None, max_length=1000, description="Optional description"
|
||||
)
|
||||
|
||||
# Relationships (foreign keys)
|
||||
owner_id: UUID | None = Field(
|
||||
default=None, foreign_key="users.id", index=True, description="Owner user ID"
|
||||
)
|
||||
|
||||
# Status flags
|
||||
is_active: bool = Field(default=True, description="Whether example is active")
|
||||
is_archived: bool = Field(default=False, description="Whether example is archived")
|
||||
|
||||
# JSON metadata field
|
||||
metadata: dict | None = Field(
|
||||
default=None,
|
||||
sa_column=Column(JSON),
|
||||
description="Flexible JSON metadata storage",
|
||||
)
|
||||
|
||||
# Numerical fields
|
||||
priority: int = Field(
|
||||
default=0, ge=0, le=10, description="Priority level (0-10)"
|
||||
)
|
||||
max_retries: int = Field(
|
||||
default=3, ge=0, description="Maximum number of retry attempts"
|
||||
)
|
||||
|
||||
# Timestamps (UTC)
|
||||
created_at: datetime = Field(
|
||||
default_factory=utc_now, description="Creation timestamp (UTC)"
|
||||
)
|
||||
updated_at: datetime = Field(
|
||||
default_factory=utc_now, description="Last update timestamp (UTC)"
|
||||
)
|
||||
archived_at: datetime | None = Field(
|
||||
default=None, description="Archive timestamp (UTC)"
|
||||
)
|
||||
|
||||
# Uncomment if using custom UTCDateTime type
|
||||
# from app.db.db_types import UTCDateTime
|
||||
# created_at: datetime = Field(
|
||||
# default_factory=utc_now,
|
||||
# sa_column=SAColumn(UTCDateTime, nullable=False)
|
||||
# )
|
||||
# updated_at: datetime = Field(
|
||||
# default_factory=utc_now,
|
||||
# sa_column=SAColumn(UTCDateTime, nullable=False, onupdate=utc_now)
|
||||
# )
|
||||
|
||||
|
||||
# Pydantic schemas for API (in separate schemas file)
|
||||
# class ExampleBase(BaseModel):
|
||||
# """Base example schema with shared fields."""
|
||||
# name: str = Field(..., max_length=255)
|
||||
# description: str | None = None
|
||||
# is_active: bool = True
|
||||
# priority: int = Field(default=0, ge=0, le=10)
|
||||
#
|
||||
# class ExampleCreate(ExampleBase):
|
||||
# """Schema for creating an example."""
|
||||
# tenant_id: UUID
|
||||
#
|
||||
# class ExampleUpdate(BaseModel):
|
||||
# """Schema for updating an example (all fields optional)."""
|
||||
# name: str | None = None
|
||||
# description: str | None = None
|
||||
# is_active: bool | None = None
|
||||
# priority: int | None = Field(None, ge=0, le=10)
|
||||
#
|
||||
# class ExampleResponse(ExampleBase):
|
||||
# """Example response schema."""
|
||||
# id: UUID
|
||||
# tenant_id: UUID
|
||||
# owner_id: UUID | None
|
||||
# created_at: datetime
|
||||
# updated_at: datetime
|
||||
#
|
||||
# model_config = ConfigDict(from_attributes=True)
|
||||
73
skills/code-style/templates/ruff.toml
Normal file
73
skills/code-style/templates/ruff.toml
Normal file
@@ -0,0 +1,73 @@
|
||||
# Grey Haven Studio - Ruff Configuration Template
|
||||
# Copy this to your project root as pyproject.toml or ruff.toml
|
||||
|
||||
[tool.ruff]
|
||||
# CRITICAL: Line length is 130, not 80 or 88!
|
||||
line-length = 130
|
||||
indent-width = 4
|
||||
|
||||
# Auto-fix issues without showing unfixable errors
|
||||
fix-only = true
|
||||
show-fixes = true
|
||||
|
||||
# Python version
|
||||
target-version = "py312"
|
||||
|
||||
[tool.ruff.lint]
|
||||
# Enable specific linter rules
|
||||
select = [
|
||||
"E", # pycodestyle errors
|
||||
"W", # pycodestyle warnings
|
||||
"F", # pyflakes
|
||||
"I", # isort (import sorting)
|
||||
"B", # flake8-bugbear (bug detection)
|
||||
"C4", # flake8-comprehensions
|
||||
"UP", # pyupgrade (automatic upgrades)
|
||||
]
|
||||
|
||||
# Ignore specific rules if needed
|
||||
ignore = []
|
||||
|
||||
[tool.ruff.format]
|
||||
# Use double quotes for strings
|
||||
quote-style = "double"
|
||||
|
||||
# Use spaces for indentation
|
||||
indent-style = "space"
|
||||
|
||||
# Use Unix-style line endings
|
||||
line-ending = "lf"
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
# Configure import sorting
|
||||
known-first-party = ["app"]
|
||||
section-order = [
|
||||
"future",
|
||||
"standard-library",
|
||||
"third-party",
|
||||
"first-party",
|
||||
"local-folder",
|
||||
]
|
||||
|
||||
# MyPy Configuration (add to pyproject.toml)
|
||||
# [tool.mypy]
|
||||
# python_version = "3.12"
|
||||
# warn_return_any = true
|
||||
# warn_unused_configs = true
|
||||
# disallow_untyped_defs = true # REQUIRED: Type hints on all functions!
|
||||
# check_untyped_defs = true
|
||||
# ignore_missing_imports = false
|
||||
# strict_optional = true
|
||||
# warn_redundant_casts = true
|
||||
# warn_unused_ignores = true
|
||||
|
||||
# Pytest Configuration (add to pyproject.toml)
|
||||
# [tool.pytest.ini_options]
|
||||
# pythonpath = ["."]
|
||||
# asyncio_mode = "auto"
|
||||
# testpaths = ["tests"]
|
||||
# markers = [
|
||||
# "unit: Unit tests",
|
||||
# "integration: Integration tests",
|
||||
# "e2e: End-to-end tests",
|
||||
# ]
|
||||
85
skills/code-style/templates/typescript-component.tsx
Normal file
85
skills/code-style/templates/typescript-component.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
// Example React Component Template
|
||||
// Copy and adapt this for new Grey Haven components
|
||||
|
||||
import { useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Button } from "~/lib/components/ui/button";
|
||||
import { Card } from "~/lib/components/ui/card";
|
||||
import { queryClient } from "~/lib/query-client";
|
||||
|
||||
// 1. Define your types/interfaces
|
||||
interface MyComponentProps {
|
||||
id: string;
|
||||
onUpdate?: (data: MyData) => void;
|
||||
}
|
||||
|
||||
interface MyData {
|
||||
id: string;
|
||||
name: string;
|
||||
created_at: Date; // snake_case for database fields
|
||||
is_active: boolean;
|
||||
}
|
||||
|
||||
// 2. Component (default export for routes)
|
||||
export default function MyComponent({ id, onUpdate }: MyComponentProps) {
|
||||
// 3. State management
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
// 4. Queries with TanStack Query
|
||||
const { data, isLoading, error } = useQuery(
|
||||
{
|
||||
queryKey: ["myData", id],
|
||||
queryFn: async () => {
|
||||
// Replace with your API call
|
||||
const response = await fetch(`/api/data/${id}`);
|
||||
return response.json();
|
||||
},
|
||||
staleTime: 60000, // 1 minute - Grey Haven default
|
||||
},
|
||||
queryClient,
|
||||
);
|
||||
|
||||
// 5. Event handlers
|
||||
const handleSave = async () => {
|
||||
// Replace with your save logic
|
||||
console.log("Saving...");
|
||||
setIsEditing(false);
|
||||
onUpdate?.(data);
|
||||
};
|
||||
|
||||
// 6. Conditional renders
|
||||
if (isLoading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <div>Error loading data</div>;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return <div>No data found</div>;
|
||||
}
|
||||
|
||||
// 7. Main render
|
||||
return (
|
||||
<Card className="p-6">
|
||||
<h2 className="mb-4 text-2xl font-bold">{data.name}</h2>
|
||||
|
||||
{isEditing ? (
|
||||
<div className="space-y-4">
|
||||
{/* Edit mode UI */}
|
||||
<Button onClick={handleSave}>Save</Button>
|
||||
<Button variant="outline" onClick={() => setIsEditing(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{/* View mode UI */}
|
||||
<p>Status: {data.is_active ? "Active" : "Inactive"}</p>
|
||||
<Button onClick={() => setIsEditing(true)}>Edit</Button>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
92
skills/code-style/templates/typescript-server-function.ts
Normal file
92
skills/code-style/templates/typescript-server-function.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
// Example TanStack Start Server Function Template
|
||||
// Copy and adapt this for new Grey Haven server functions
|
||||
|
||||
import { createServerFn } from "@tanstack/start";
|
||||
import { db } from "~/lib/server/db";
|
||||
import { users } from "~/lib/server/schema/users";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
|
||||
// 1. Define input schema with Zod
|
||||
const getUserInputSchema = z.object({
|
||||
userId: z.string().uuid(),
|
||||
tenantId: z.string(), // Always include tenant_id for multi-tenant isolation
|
||||
});
|
||||
|
||||
// 2. Define output type
|
||||
interface UserOutput {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
created_at: Date;
|
||||
tenant_id: string;
|
||||
}
|
||||
|
||||
// 3. Create server function
|
||||
export const getUser = createServerFn("GET", async (input: unknown): Promise<UserOutput> => {
|
||||
// Validate input
|
||||
const { userId, tenantId } = getUserInputSchema.parse(input);
|
||||
|
||||
// Query database with tenant isolation
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.id, userId) && eq(users.tenant_id, tenantId), // Tenant filtering!
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new Error("User not found");
|
||||
}
|
||||
|
||||
// Return typed result
|
||||
return {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
created_at: user.created_at,
|
||||
tenant_id: user.tenant_id,
|
||||
};
|
||||
});
|
||||
|
||||
// 4. Mutation example
|
||||
const updateUserInputSchema = z.object({
|
||||
userId: z.string().uuid(),
|
||||
tenantId: z.string(),
|
||||
name: z.string().min(1),
|
||||
email: z.string().email(),
|
||||
});
|
||||
|
||||
export const updateUser = createServerFn(
|
||||
"POST",
|
||||
async (input: unknown): Promise<UserOutput> => {
|
||||
// Validate input
|
||||
const { userId, tenantId, name, email } = updateUserInputSchema.parse(input);
|
||||
|
||||
// Update with tenant isolation
|
||||
const [updatedUser] = await db
|
||||
.update(users)
|
||||
.set({ name, email, updated_at: new Date() })
|
||||
.where(eq(users.id, userId) && eq(users.tenant_id, tenantId)) // Tenant filtering!
|
||||
.returning();
|
||||
|
||||
if (!updatedUser) {
|
||||
throw new Error("User not found or update failed");
|
||||
}
|
||||
|
||||
return {
|
||||
id: updatedUser.id,
|
||||
name: updatedUser.name,
|
||||
email: updatedUser.email,
|
||||
created_at: updatedUser.created_at,
|
||||
tenant_id: updatedUser.tenant_id,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// 5. Usage in client components
|
||||
// import { useQuery } from "@tanstack/react-query";
|
||||
// import { getUser } from "~/lib/server/functions/users";
|
||||
//
|
||||
// const { data: user } = useQuery({
|
||||
// queryKey: ["user", userId],
|
||||
// queryFn: () => getUser({ userId, tenantId }),
|
||||
// staleTime: 60000,
|
||||
// });
|
||||
Reference in New Issue
Block a user