feat: add Next.js, Vue/Nuxt, React, Python (Django/FastAPI) skills and agents
- python-developer agent: Django/FastAPI backend specialist - nextjs-patterns skill: App Router, Server Components, Server Actions, Auth.js - vue-nuxt-patterns skill: Composition API, Pinia, Nitro server, SSR - react-patterns skill: hooks, Context, TanStack Query, React Hook Form - python-django-patterns skill: DRF, services, repositories - python-fastapi-patterns skill: async, Pydantic, SQLAlchemy, dependencies - /nextjs pipeline command for full-stack Next.js apps - /vue pipeline command for full-stack Vue/Nuxt apps - Updated frontend-developer with framework-specific skills - Updated orchestrator, capability-index for Python + frontend routing - Updated README, STRUCTURE, EVOLUTION_LOG with all new stacks Total agents: 30. Stacks: PHP, Next.js, Vue/Nuxt, React, Python, Go, Flutter, Node.js
This commit is contained in:
@@ -326,3 +326,81 @@ Capability Expansion + Architecture Improvements — 7 evolutionary tasks
|
||||
- New rules: 4 (atomic-tasks, modular-code, token-optimization, gitea-centric)
|
||||
- Hardcoded APAW refs fixed: 15+ across 5 files
|
||||
- Documentation pages updated: 3 (README, STRUCTURE, EVOLUTION_LOG)
|
||||
|
||||
---
|
||||
|
||||
## Entry: 2026-04-19T10:00:00+01:00
|
||||
|
||||
### Type
|
||||
Capability Expansion — Frontend framework skills + Python development stack
|
||||
|
||||
### Gap Analysis
|
||||
1. No Next.js patterns — most popular full-stack React framework
|
||||
2. No Vue/Nuxt patterns — major frontend framework
|
||||
3. No React-only patterns — base for Next.js and many SPAs
|
||||
4. No Python backend support (Django, FastAPI)
|
||||
5. Frontend developer had no framework-specific skills
|
||||
|
||||
### Implementation
|
||||
|
||||
#### New Agent
|
||||
| Agent | Model | Purpose |
|
||||
|-------|-------|---------|
|
||||
| `python-developer` | qwen3-coder:480b | Python/Django/FastAPI backend |
|
||||
|
||||
#### New Skills (5)
|
||||
| Skill | Lines | Purpose |
|
||||
|-------|-------|---------|
|
||||
| `nextjs-patterns` | 290 | Next.js 14+ App Router, Server Components, Server Actions, Auth.js, API Routes |
|
||||
| `vue-nuxt-patterns` | 270 | Vue 3 / Nuxt 3 Composition API, Pinia, Nitro server, SSR |
|
||||
| `react-patterns` | 240 | React 18+ hooks, Context, TanStack Query, React Hook Form |
|
||||
| `python-django-patterns` | 200 | Django models, DRF serializers, services, repositories |
|
||||
| `python-fastapi-patterns` | 230 | FastAPI async, Pydantic schemas, SQLAlchemy, dependencies |
|
||||
|
||||
#### New Commands
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `/nextjs` | Full-stack Next.js 14+ app pipeline |
|
||||
| `/vue` | Full-stack Vue/Nuxt 3 app pipeline |
|
||||
|
||||
#### Updated Agent
|
||||
| Agent | Change |
|
||||
|-------|--------|
|
||||
| `frontend-developer` | Added skills: nextjs-patterns, vue-nuxt-patterns, react-patterns |
|
||||
|
||||
#### Updated Config
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `orchestrator.md` | Added python-developer permission + delegation |
|
||||
| `capability-index.yaml` | Added python-developer + frontend framework capabilities + routing |
|
||||
|
||||
### Files Modified
|
||||
- `.kilo/agents/orchestrator.md` — python-developer permission + delegation
|
||||
- `.kilo/agents/frontend-developer.md` — framework skills table
|
||||
- `.kilo/capability-index.yaml` — python-developer + frontend routing
|
||||
- `AGENTS.md` — python-developer, frontend update, new commands
|
||||
|
||||
### New Files Created
|
||||
- `.kilo/agents/python-developer.md`
|
||||
- `.kilo/commands/nextjs.md`
|
||||
- `.kilo/commands/vue.md`
|
||||
- `.kilo/skills/nextjs-patterns/SKILL.md`
|
||||
- `.kilo/skills/vue-nuxt-patterns/SKILL.md`
|
||||
- `.kilo/skills/react-patterns/SKILL.md`
|
||||
- `.kilo/skills/python-django-patterns/SKILL.md`
|
||||
- `.kilo/skills/python-fastapi-patterns/SKILL.md`
|
||||
|
||||
### Verification
|
||||
- [x] Python developer agent created with valid YAML frontmatter
|
||||
- [x] Orchestrator permissions updated for python-developer
|
||||
- [x] Capability index updated with python + frontend routing
|
||||
- [x] Frontend developer has framework-specific skills
|
||||
- [x] YAML validated (capability-index.yaml)
|
||||
- [x] README updated with all frameworks
|
||||
- [x] STRUCTURE updated with all skills
|
||||
|
||||
### Metrics
|
||||
- New agents: 1 (python-developer, total now 30)
|
||||
- New skills: 5 (3 frontend + 2 Python)
|
||||
- New commands: 2 (nextjs, vue)
|
||||
- Supported stacks: PHP, Next.js, Vue/Nuxt, React, Python, Go, Flutter, Node.js
|
||||
|
||||
@@ -27,6 +27,7 @@ UI specialist: implement from screenshots/mockups, responsive, accessible, pixel
|
||||
- Accessibility first: semantic HTML, ARIA labels, keyboard navigation
|
||||
- Responsive by default: mobile-first approach
|
||||
- Component composition: build small, reusable parts
|
||||
- Framework-aware: Next.js App Router, Vue/Nuxt Composition API, React hooks
|
||||
|
||||
## Delegates
|
||||
| Agent | When |
|
||||
@@ -43,6 +44,14 @@ UI specialist: implement from screenshots/mockups, responsive, accessible, pixel
|
||||
<files><!-- list: all created/modified files --></files>
|
||||
</impl>
|
||||
|
||||
## Skills
|
||||
| Skill | When |
|
||||
|-------|------|
|
||||
| nextjs-patterns | Next.js 14+ App Router, Server Components, Server Actions |
|
||||
| vue-nuxt-patterns | Vue 3 / Nuxt 3 Composition API, Pinia, SSR |
|
||||
| react-patterns | React 18+ hooks, context, TanStack Query |
|
||||
| flutter-widgets | Flutter widget patterns |
|
||||
|
||||
## Handoff
|
||||
1. Verify visual match to design
|
||||
2. Check accessibility
|
||||
|
||||
@@ -42,6 +42,7 @@ permission:
|
||||
"memory-manager": allow
|
||||
"agent-architect": allow
|
||||
"php-developer": allow
|
||||
"python-developer": allow
|
||||
---
|
||||
|
||||
# Orchestrator
|
||||
@@ -72,6 +73,7 @@ Task dispatcher and state machine manager. Route by issue status; enforce workfl
|
||||
| frontend-developer | UI implementation needed |
|
||||
| backend-developer | Node.js/Express/API work |
|
||||
| php-developer | PHP/Laravel/Symfony/WordPress web apps |
|
||||
| python-developer | Python/Django/FastAPI/API work |
|
||||
| go-developer | Go backend services |
|
||||
| flutter-developer | Flutter mobile apps |
|
||||
| performance-engineer | Review pass: check performance |
|
||||
|
||||
62
.kilo/agents/python-developer.md
Normal file
62
.kilo/agents/python-developer.md
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
description: Python backend specialist for Django, FastAPI, data science, and API development
|
||||
mode: subagent
|
||||
model: ollama-cloud/qwen3-coder:480b
|
||||
variant: thinking
|
||||
color: "#3776AB"
|
||||
permission:
|
||||
read: allow
|
||||
edit: allow
|
||||
write: allow
|
||||
bash: allow
|
||||
glob: allow
|
||||
grep: allow
|
||||
task:
|
||||
"*": deny
|
||||
"code-skeptic": allow
|
||||
"security-auditor": allow
|
||||
"orchestrator": allow
|
||||
---
|
||||
|
||||
# Python Developer
|
||||
|
||||
## Role
|
||||
Python backend specialist: Django/FastAPI APIs, database integration, async patterns, authentication, modular architecture.
|
||||
|
||||
## Behavior
|
||||
- Security first: validate input, parameterized queries, auth middleware
|
||||
- RESTful design: proper HTTP methods, status codes, error handling
|
||||
- Async with FastAPI, sync with Django — follow framework conventions
|
||||
- Type hints everywhere, Pydantic for validation
|
||||
- Separate services/repositories from routes/views
|
||||
- Write tests with pytest before implementation (TDD)
|
||||
|
||||
## Delegates
|
||||
| Agent | When |
|
||||
|-------|------|
|
||||
| code-skeptic | After implementation |
|
||||
| security-auditor | For security review |
|
||||
|
||||
## Output
|
||||
<impl agent="python-developer">
|
||||
<endpoints><!-- table: method, path, description --></endpoints>
|
||||
<database><!-- table, columns, indexes --></database>
|
||||
<files><!-- list: all created/modified files --></files>
|
||||
<security><!-- checklist: validation, injection protection, auth --></security>
|
||||
</impl>
|
||||
|
||||
## Skills
|
||||
| Skill | When |
|
||||
|-------|------|
|
||||
| python-django-patterns | Django models, DRF, services, repositories |
|
||||
| python-fastapi-patterns | FastAPI routes, Pydantic, async, dependencies |
|
||||
| php-security | OWASP common patterns (shared with PHP) |
|
||||
| php-testing | pytest patterns (adapted for Python) |
|
||||
|
||||
## Handoff
|
||||
1. Run `pytest` with coverage
|
||||
2. Run `ruff check .` for linting
|
||||
3. Run `mypy .` for type checking
|
||||
4. Delegate: code-skeptic
|
||||
|
||||
<gitea-commenting required="true" skill="gitea-commenting" />
|
||||
@@ -32,12 +32,18 @@ agents:
|
||||
- component_creation
|
||||
- styling
|
||||
- responsive_design
|
||||
- nextjs_development
|
||||
- vue_nuxt_development
|
||||
- react_development
|
||||
receives:
|
||||
- designs
|
||||
- wireframes
|
||||
- api_endpoints
|
||||
produces:
|
||||
- vue_components
|
||||
- react_components
|
||||
- nextjs_pages
|
||||
- nuxt_pages
|
||||
- css_styles
|
||||
- frontend_tests
|
||||
forbidden:
|
||||
@@ -84,6 +90,39 @@ agents:
|
||||
- security-auditor
|
||||
- orchestrator
|
||||
|
||||
python-developer:
|
||||
capabilities:
|
||||
- python_web_development
|
||||
- django_development
|
||||
- fastapi_development
|
||||
- python_api_development
|
||||
- python_database_design
|
||||
- python_authentication
|
||||
- python_async_patterns
|
||||
- python_testing
|
||||
- python_security
|
||||
receives:
|
||||
- api_specifications
|
||||
- database_requirements
|
||||
produces:
|
||||
- django_views
|
||||
- fastapi_routers
|
||||
- python_models
|
||||
- python_services
|
||||
- python_schemas
|
||||
- python_migrations
|
||||
- python_tests
|
||||
forbidden:
|
||||
- frontend_code
|
||||
- non_python_backend
|
||||
model: ollama-cloud/qwen3-coder:480b
|
||||
variant: thinking
|
||||
mode: subagent
|
||||
delegates_to:
|
||||
- code-skeptic
|
||||
- security-auditor
|
||||
- orchestrator
|
||||
|
||||
backend-developer:
|
||||
capabilities:
|
||||
- api_development
|
||||
@@ -459,6 +498,7 @@ agents:
|
||||
- frontend-developer
|
||||
- backend-developer
|
||||
- php-developer
|
||||
- python-developer
|
||||
- go-developer
|
||||
- flutter-developer
|
||||
- performance-engineer
|
||||
@@ -714,6 +754,9 @@ agents:
|
||||
bug_fixing: the-fixer
|
||||
git_operations: release-manager
|
||||
ui_implementation: frontend-developer
|
||||
nextjs_development: frontend-developer
|
||||
vue_nuxt_development: frontend-developer
|
||||
react_development: frontend-developer
|
||||
e2e_testing: browser-automation
|
||||
visual_testing: visual-tester
|
||||
bbox_extraction: visual-tester
|
||||
@@ -741,6 +784,10 @@ agents:
|
||||
laravel_development: php-developer
|
||||
symfony_development: php-developer
|
||||
wordpress_development: php-developer
|
||||
# Python Development
|
||||
python_web_development: python-developer
|
||||
django_development: python-developer
|
||||
fastapi_development: python-developer
|
||||
# DevOps
|
||||
docker_configuration: devops-engineer
|
||||
kubernetes_setup: devops-engineer
|
||||
|
||||
118
.kilo/commands/nextjs.md
Normal file
118
.kilo/commands/nextjs.md
Normal file
@@ -0,0 +1,118 @@
|
||||
---
|
||||
description: Full-stack Next.js web application pipeline with App Router, SSR, and authentication
|
||||
mode: nextjs
|
||||
model: ollama-cloud/qwen3-coder:480b
|
||||
variant: thinking
|
||||
color: "#0EA5E9"
|
||||
permission:
|
||||
read: allow
|
||||
edit: allow
|
||||
write: allow
|
||||
bash: allow
|
||||
glob: allow
|
||||
grep: allow
|
||||
task:
|
||||
"*": deny
|
||||
"frontend-developer": allow
|
||||
"backend-developer": allow
|
||||
"system-analyst": allow
|
||||
"lead-developer": allow
|
||||
"sdet-engineer": allow
|
||||
"code-skeptic": allow
|
||||
"the-fixer": allow
|
||||
"devops-engineer": allow
|
||||
"release-manager": allow
|
||||
"security-auditor": allow
|
||||
"orchestrator": allow
|
||||
---
|
||||
|
||||
# Next.js Web Application Pipeline
|
||||
|
||||
Create a full-stack Next.js 14+ application with App Router, Server Components, API routes, Auth.js, and Docker deployment. Follows atomic task decomposition.
|
||||
|
||||
## Parameters
|
||||
|
||||
- `project_name`: Application name (required)
|
||||
- `auth`: Auth provider - 'authjs', 'clerk', 'supabase' (default: 'authjs')
|
||||
- `database`: Database - 'prisma', 'drizzle' (default: 'prisma')
|
||||
- `ui`: UI library - 'tailwind', 'shadcn', 'mui' (default: 'shadcn')
|
||||
- `docker`: Create Docker deployment (default: true)
|
||||
- `issue`: Gitea issue number for tracking (required)
|
||||
|
||||
## Overview
|
||||
|
||||
```
|
||||
Requirements → Architecture → Setup → Pages → API → Auth → Frontend → Tests → Docker
|
||||
```
|
||||
|
||||
## Atomic Task Decomposition
|
||||
|
||||
### Step 1: Requirements (1 task)
|
||||
**Agent**: `@requirement-refiner` — Create issue in TARGET PROJECT
|
||||
|
||||
### Step 2: Architecture (1 task)
|
||||
**Agent**: `@system-analyst` — Design routes, API, database schema
|
||||
|
||||
### Step 3: Project Setup (1 task)
|
||||
**Agent**: `@frontend-developer`
|
||||
```bash
|
||||
npx create-next-app@latest {project_name} --typescript --tailwind --eslint --app --src-dir
|
||||
cd {project_name}
|
||||
npx shadcn@latest init
|
||||
```
|
||||
|
||||
### Step 4: Database + Models (1 task per model)
|
||||
**Agent**: `@backend-developer` or `@frontend-developer`
|
||||
- Prisma schema or Drizzle definitions
|
||||
- Run `npx prisma migrate dev`
|
||||
|
||||
### Step 5: API Routes (1 task per resource)
|
||||
**Agent**: `@backend-developer` (ONE invocation per resource)
|
||||
- GET, POST, PUT, DELETE handlers
|
||||
- Zod validation schemas
|
||||
|
||||
### Step 6: Authentication (1 task)
|
||||
**Agent**: `@frontend-developer`
|
||||
- Auth.js / Clerk / Supabase setup
|
||||
- Login/Register pages
|
||||
- Middleware for protected routes
|
||||
|
||||
### Step 7: UI Pages (1 task per page/layout)
|
||||
**Agent**: `@frontend-developer` (ONE invocation per page)
|
||||
- Server Components by default
|
||||
- `'use client'` only for interactivity
|
||||
- Shadcn UI components
|
||||
|
||||
### Step 8: Server Actions (1 task per form)
|
||||
**Agent**: `@frontend-developer`
|
||||
- Form validation with Zod
|
||||
- `revalidatePath` after mutations
|
||||
|
||||
### Step 9: Tests (1 task per test suite)
|
||||
**Agent**: `@sdet-engineer` — Vitest + Playwright
|
||||
|
||||
### Step 10: Review → Security → Docker → Release
|
||||
|
||||
## Task Sizing
|
||||
|
||||
| Task | Agent | Max Tokens |
|
||||
|------|-------|-----------|
|
||||
| Setup project | frontend-developer | 5,000 |
|
||||
| Database schema | backend-developer | 5,000 |
|
||||
| API route (CRUD) | backend-developer | 5,000 |
|
||||
| Auth setup | frontend-developer | 8,000 |
|
||||
| Page + components | frontend-developer | 8,000 |
|
||||
| Server actions | frontend-developer | 5,000 |
|
||||
| Tests | sdet-engineer | 8,000 |
|
||||
| Docker | devops-engineer | 5,000 |
|
||||
|
||||
## Quality Gates
|
||||
|
||||
| Gate | Criteria |
|
||||
|------|----------|
|
||||
| Setup | `npm run build` succeeds |
|
||||
| API | All endpoints return correct responses |
|
||||
| Auth | Login/register/logout work |
|
||||
| Pages | Lighthouse ≥ 90 |
|
||||
| Tests | Coverage ≥ 80% |
|
||||
| Docker | Containers build and run |
|
||||
107
.kilo/commands/vue.md
Normal file
107
.kilo/commands/vue.md
Normal file
@@ -0,0 +1,107 @@
|
||||
---
|
||||
description: Full-stack Vue/Nuxt web application pipeline with SSR, Pinia, and Nitro server
|
||||
mode: vue
|
||||
model: ollama-cloud/qwen3-coder:480b
|
||||
variant: thinking
|
||||
color: "#42B883"
|
||||
permission:
|
||||
read: allow
|
||||
edit: allow
|
||||
write: allow
|
||||
bash: allow
|
||||
glob: allow
|
||||
grep: allow
|
||||
task:
|
||||
"*": deny
|
||||
"frontend-developer": allow
|
||||
"backend-developer": allow
|
||||
"system-analyst": allow
|
||||
"lead-developer": allow
|
||||
"sdet-engineer": allow
|
||||
"code-skeptic": allow
|
||||
"the-fixer": allow
|
||||
"devops-engineer": allow
|
||||
"release-manager": allow
|
||||
"security-auditor": allow
|
||||
"orchestrator": allow
|
||||
---
|
||||
|
||||
# Vue/Nuxt Web Application Pipeline
|
||||
|
||||
Create a full-stack Nuxt 3 application with Composition API, Pinia, server API routes, and Docker deployment.
|
||||
|
||||
## Parameters
|
||||
|
||||
- `project_name`: Application name (required)
|
||||
- `ui`: UI library - 'tailwind', 'vuetify', 'primevue' (default: 'tailwind')
|
||||
- `auth`: Auth - 'local', 'supabase', 'firebase' (default: 'local')
|
||||
- `database`: Database - 'prisma', 'drizzle' (default: 'prisma')
|
||||
- `docker`: Create Docker deployment (default: true)
|
||||
- `issue`: Gitea issue number for tracking (required)
|
||||
|
||||
## Overview
|
||||
|
||||
```
|
||||
Requirements → Architecture → Setup → Pages → Server API → Auth → Components → Tests → Docker
|
||||
```
|
||||
|
||||
## Atomic Task Decomposition
|
||||
|
||||
### Step 1: Requirements (1 task)
|
||||
**Agent**: `@requirement-refiner` — Create issue in TARGET PROJECT
|
||||
|
||||
### Step 2: Architecture (1 task)
|
||||
**Agent**: `@system-analyst` — Design pages, API routes, database schema
|
||||
|
||||
### Step 3: Project Setup (1 task)
|
||||
**Agent**: `@frontend-developer`
|
||||
```bash
|
||||
npx nuxi@latest init {project_name}
|
||||
cd {project_name}
|
||||
npx nuxi module add @pinia/nuxt
|
||||
npx nuxi module add @nuxtjs/tailwindcss
|
||||
```
|
||||
|
||||
### Step 4: Server API Routes (1 task per resource)
|
||||
**Agent**: `@backend-developer` or `@frontend-developer`
|
||||
- `server/api/products/index.get.ts`
|
||||
- `server/api/products/[id].get.ts`
|
||||
- `server/api/products/index.post.ts`
|
||||
|
||||
### Step 5: Pinia Stores (1 task per store)
|
||||
**Agent**: `@frontend-developer`
|
||||
- `stores/auth.ts`
|
||||
- `stores/cart.ts`
|
||||
|
||||
### Step 6: Composables (1 task per composable)
|
||||
**Agent**: `@frontend-developer`
|
||||
- `composables/useAuth.ts`
|
||||
- `composables/useCart.ts`
|
||||
|
||||
### Step 7: Pages + Layouts (1 task per page)
|
||||
**Agent**: `@frontend-developer` (ONE invocation per page)
|
||||
- `<script setup lang="ts">` with Composition API
|
||||
- `useFetch()` for data loading
|
||||
- NuxtLink for navigation
|
||||
|
||||
### Step 8: Components (1 task per component)
|
||||
**Agent**: `@frontend-developer`
|
||||
|
||||
### Step 9: Route Middleware (1 task)
|
||||
**Agent**: `@frontend-developer`
|
||||
|
||||
### Step 10: Tests (1 task per suite)
|
||||
**Agent**: `@sdet-engineer` — Vitest + Playwright
|
||||
|
||||
### Step 11: Review → Security → Docker → Release
|
||||
|
||||
## Quality Gates
|
||||
|
||||
| Gate | Criteria |
|
||||
|------|----------|
|
||||
| Setup | `npm run build` succeeds |
|
||||
| API | All server routes return correct responses |
|
||||
| Auth | Login/logout work, middleware protects |
|
||||
| Pages | SSR renders correctly |
|
||||
| Tests | Coverage ≥ 80% |
|
||||
| Docker | Containers build and run |
|
||||
301
.kilo/skills/nextjs-patterns/SKILL.md
Normal file
301
.kilo/skills/nextjs-patterns/SKILL.md
Normal file
@@ -0,0 +1,301 @@
|
||||
---
|
||||
name: nextjs-patterns
|
||||
description: Next.js 14+ patterns — App Router, Server Components, Server Actions, API Routes, Auth.js, middleware, ISR/SSR/SSG
|
||||
---
|
||||
|
||||
# Next.js Patterns
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── app/ # App Router (Next.js 14+)
|
||||
│ ├── layout.tsx # Root layout
|
||||
│ ├── page.tsx # Home page
|
||||
│ ├── loading.tsx # Loading UI
|
||||
│ ├── error.tsx # Error boundary
|
||||
│ ├── not-found.tsx # 404 page
|
||||
│ ├── globals.css # Global styles
|
||||
│ ├── (auth)/ # Route group: auth
|
||||
│ │ ├── login/page.tsx
|
||||
│ │ └── register/page.tsx
|
||||
│ ├── (dashboard)/ # Route group: dashboard
|
||||
│ │ ├── layout.tsx # Dashboard layout (sidebar)
|
||||
│ │ ├── page.tsx # Dashboard home
|
||||
│ │ ├── products/
|
||||
│ │ │ ├── page.tsx # Product list
|
||||
│ │ │ └── [id]/page.tsx # Product detail
|
||||
│ │ └── settings/page.tsx
|
||||
│ └── api/ # API Routes
|
||||
│ ├── auth/[...nextauth]/route.ts
|
||||
│ ├── products/route.ts
|
||||
│ └── webhooks/stripe/route.ts
|
||||
├── components/
|
||||
│ ├── ui/ # Base UI (Button, Input, Card, Dialog)
|
||||
│ ├── forms/ # Form components
|
||||
│ ├── layouts/ # Layout components
|
||||
│ └── features/ # Feature-specific components
|
||||
├── lib/
|
||||
│ ├── api.ts # API client (fetch wrapper)
|
||||
│ ├── auth.ts # Auth configuration
|
||||
│ ├── db.ts # Database client
|
||||
│ └── utils.ts # Utilities
|
||||
├── actions/ # Server Actions
|
||||
│ ├── products.ts
|
||||
│ └── auth.ts
|
||||
├── types/ # TypeScript types
|
||||
└── middleware.ts # Route middleware
|
||||
```
|
||||
|
||||
## App Router Patterns
|
||||
|
||||
### Server Component (default)
|
||||
|
||||
```tsx
|
||||
// app/products/page.tsx — Server Component (default in app/)
|
||||
import { prisma } from '@/lib/db';
|
||||
|
||||
export default async function ProductsPage() {
|
||||
const products = await prisma.product.findMany({
|
||||
where: { isActive: true },
|
||||
include: { category: true },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{products.map((product) => (
|
||||
<ProductCard key={product.id} product={product} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Client Component (interactive)
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
export function ProductFilters({ categories, onFilter }: Props) {
|
||||
const [selected, setSelected] = useState<string[]>([]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{categories.map((cat) => (
|
||||
<button
|
||||
key={cat.id}
|
||||
onClick={() => {
|
||||
const next = selected.includes(cat.id)
|
||||
? selected.filter((s) => s !== cat.id)
|
||||
: [...selected, cat.id];
|
||||
setSelected(next);
|
||||
onFilter(next);
|
||||
}}
|
||||
className={cn('px-3 py-1 rounded-full text-sm', {
|
||||
'bg-blue-600 text-white': selected.includes(cat.id),
|
||||
'bg-gray-100 text-gray-700': !selected.includes(cat.id),
|
||||
})}
|
||||
>
|
||||
{cat.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Dynamic Route with Suspense
|
||||
|
||||
```tsx
|
||||
// app/products/[id]/page.tsx
|
||||
import { Suspense } from 'react';
|
||||
|
||||
async function ProductDetail({ id }: { id: string }) {
|
||||
const product = await getProduct(id);
|
||||
return <ProductView product={product} />;
|
||||
}
|
||||
|
||||
export default function Page({ params }: { params: { id: string } }) {
|
||||
return (
|
||||
<Suspense fallback={<ProductSkeleton />}>
|
||||
<ProductDetail id={params.id} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Server Actions
|
||||
|
||||
```tsx
|
||||
// actions/products.ts
|
||||
'use server';
|
||||
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { productSchema } from '@/types/product';
|
||||
|
||||
export async function createProduct(formData: FormData) {
|
||||
const raw = Object.fromEntries(formData);
|
||||
const data = productSchema.parse(raw);
|
||||
|
||||
const product = await prisma.product.create({ data });
|
||||
revalidatePath('/products');
|
||||
return product;
|
||||
}
|
||||
|
||||
export async function deleteProduct(id: string) {
|
||||
await prisma.product.delete({ where: { id } });
|
||||
revalidatePath('/products');
|
||||
}
|
||||
```
|
||||
|
||||
## API Routes
|
||||
|
||||
```ts
|
||||
// app/api/products/route.ts
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { z } from 'zod';
|
||||
|
||||
const createSchema = z.object({
|
||||
name: z.string().min(1).max(255),
|
||||
price: z.number().positive(),
|
||||
categoryId: z.string().cuid(),
|
||||
});
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const limit = parseInt(searchParams.get('limit') || '20');
|
||||
|
||||
const [products, total] = await Promise.all([
|
||||
prisma.product.findMany({
|
||||
skip: (page - 1) * limit,
|
||||
take: limit,
|
||||
include: { category: true },
|
||||
}),
|
||||
prisma.product.count(),
|
||||
]);
|
||||
|
||||
return NextResponse.json({
|
||||
data: products,
|
||||
meta: { page, limit, total, pages: Math.ceil(total / limit) },
|
||||
});
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const body = await req.json();
|
||||
const data = createSchema.parse(body);
|
||||
const product = await prisma.product.create({ data });
|
||||
return NextResponse.json(product, { status: 201 });
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication (Auth.js / NextAuth v5)
|
||||
|
||||
```ts
|
||||
// lib/auth.ts
|
||||
import NextAuth from 'next-auth';
|
||||
import GitHub from 'next-auth/providers/github';
|
||||
import Credentials from 'next-auth/providers/credentials';
|
||||
import { prisma } from '@/lib/db';
|
||||
|
||||
export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
providers: [
|
||||
GitHub,
|
||||
Credentials({
|
||||
credentials: {
|
||||
email: { label: 'Email', type: 'email' },
|
||||
password: { label: 'Password', type: 'password' },
|
||||
},
|
||||
async authorize(credentials) {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: credentials.email as string },
|
||||
});
|
||||
if (!user || !verifyPassword(credentials.password as string, user.password)) {
|
||||
return null;
|
||||
}
|
||||
return user;
|
||||
},
|
||||
}),
|
||||
],
|
||||
pages: {
|
||||
signIn: '/login',
|
||||
signUp: '/register',
|
||||
},
|
||||
});
|
||||
|
||||
// app/api/auth/[...nextauth]/route.ts
|
||||
import { handlers } from '@/lib/auth';
|
||||
export const { GET, POST } = handlers;
|
||||
```
|
||||
|
||||
## Middleware
|
||||
|
||||
```ts
|
||||
// middleware.ts
|
||||
import { auth } from '@/lib/auth';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export default auth((req) => {
|
||||
const isAuth = !!req.auth;
|
||||
const isAuthPage = req.nextUrl.pathname.startsWith('/login') ||
|
||||
req.nextUrl.pathname.startsWith('/register');
|
||||
const isProtected = req.nextUrl.pathname.startsWith('/dashboard');
|
||||
|
||||
if (isProtected && !isAuth) {
|
||||
return NextResponse.redirect(new URL('/login', req.url));
|
||||
}
|
||||
|
||||
if (isAuthPage && isAuth) {
|
||||
return NextResponse.redirect(new URL('/dashboard', req.url));
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
});
|
||||
|
||||
export const config = {
|
||||
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
|
||||
};
|
||||
```
|
||||
|
||||
## Data Fetching Patterns
|
||||
|
||||
```tsx
|
||||
// Parallel data fetching
|
||||
async function DashboardPage() {
|
||||
const [stats, recentOrders, topProducts] = await Promise.all([
|
||||
getDashboardStats(),
|
||||
getRecentOrders(10),
|
||||
getTopProducts(5),
|
||||
]);
|
||||
return <DashboardView stats={stats} orders={recentOrders} products={topProducts} />;
|
||||
}
|
||||
|
||||
// ISR (Incremental Static Regeneration)
|
||||
export const revalidate = 3600; // revalidate every hour
|
||||
|
||||
// SSG with generateStaticParams
|
||||
export async function generateStaticParams() {
|
||||
const products = await prisma.product.findMany({ select: { id: true } });
|
||||
return products.map((p) => ({ id: p.id }));
|
||||
}
|
||||
```
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] App Router (not Pages Router) for new projects
|
||||
- [ ] Server Components by default, `'use client'` only for interactivity
|
||||
- [ ] Server Actions for mutations (not API routes for forms)
|
||||
- [ ] Auth.js v5 for authentication
|
||||
- [ ] Zod for input validation
|
||||
- [ ] Suspense boundaries for async components
|
||||
- [ ] Route groups `(groupName)` for layout organization
|
||||
- [ ] `next/image` for all images with proper sizing
|
||||
- [ ] Middleware for auth checks
|
||||
- [ ] `revalidatePath` / `revalidateTag` after mutations
|
||||
- [ ] Parallel data fetching with `Promise.all`
|
||||
- [ ] Error boundaries (`error.tsx`) and loading states (`loading.tsx`)
|
||||
243
.kilo/skills/python-django-patterns/SKILL.md
Normal file
243
.kilo/skills/python-django-patterns/SKILL.md
Normal file
@@ -0,0 +1,243 @@
|
||||
---
|
||||
name: python-django-patterns
|
||||
description: Django 5+ patterns — models, views, serializers, REST framework, authentication, management commands
|
||||
---
|
||||
|
||||
# Python Django Patterns
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
project/
|
||||
├── manage.py
|
||||
├── config/ # Project settings
|
||||
│ ├── settings/
|
||||
│ │ ├── base.py
|
||||
│ │ ├── development.py
|
||||
│ │ └── production.py
|
||||
│ ├── urls.py
|
||||
│ ├── wsgi.py
|
||||
│ └── asgi.py
|
||||
├── apps/
|
||||
│ ├── products/
|
||||
│ │ ├── models.py
|
||||
│ │ ├── views.py # API views (thin)
|
||||
│ │ ├── serializers.py # DRF serializers
|
||||
│ │ ├── urls.py
|
||||
│ │ ├── services.py # Business logic
|
||||
│ │ ├── repositories.py # Data access
|
||||
│ │ ├── admin.py
|
||||
│ │ ├── tasks.py # Celery tasks
|
||||
│ │ ├── tests/
|
||||
│ │ │ ├── test_models.py
|
||||
│ │ │ ├── test_views.py
|
||||
│ │ │ └── test_services.py
|
||||
│ │ └── migrations/
|
||||
│ ├── orders/
|
||||
│ └── users/
|
||||
├── shared/
|
||||
│ ├── pagination.py
|
||||
│ ├── permissions.py
|
||||
│ ├── throttling.py
|
||||
│ └── exceptions.py
|
||||
├── requirements/
|
||||
│ ├── base.txt
|
||||
│ ├── development.txt
|
||||
│ └── production.txt
|
||||
└── docker-compose.yml
|
||||
```
|
||||
|
||||
## Model Pattern
|
||||
|
||||
```python
|
||||
# apps/products/models.py
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
class Product(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
slug = models.SlugField(unique=True)
|
||||
description = models.TextField(blank=True)
|
||||
price = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
category = models.ForeignKey('Category', on_delete=models.PROTECT, related_name='products')
|
||||
is_active = models.BooleanField(default=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created_at']
|
||||
indexes = [
|
||||
models.Index(fields=['slug']),
|
||||
models.Index(fields=['category', 'is_active']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('product-detail', kwargs={'slug': self.slug})
|
||||
```
|
||||
|
||||
## Service Pattern (Business Logic in services.py)
|
||||
|
||||
```python
|
||||
# apps/products/services.py
|
||||
from .repositories import ProductRepository
|
||||
|
||||
|
||||
class ProductService:
|
||||
def __init__(self, repository=None):
|
||||
self.repository = repository or ProductRepository()
|
||||
|
||||
def list_active(self, page=1, per_page=20, category_id=None, search=None):
|
||||
return self.repository.list_active(
|
||||
page=page, per_page=per_page,
|
||||
category_id=category_id, search=search,
|
||||
)
|
||||
|
||||
def create(self, data: dict):
|
||||
product = self.repository.create(data)
|
||||
return product
|
||||
|
||||
def update_price(self, product_id: int, new_price):
|
||||
product = self.repository.get_by_id(product_id)
|
||||
product.price = new_price
|
||||
self.repository.save(product)
|
||||
return product
|
||||
```
|
||||
|
||||
## Repository Pattern
|
||||
|
||||
```python
|
||||
# apps/products/repositories.py
|
||||
from .models import Product
|
||||
|
||||
|
||||
class ProductRepository:
|
||||
def list_active(self, page=1, per_page=20, category_id=None, search=None):
|
||||
qs = Product.objects.filter(is_active=True).select_related('category')
|
||||
if category_id:
|
||||
qs = qs.filter(category_id=category_id)
|
||||
if search:
|
||||
qs = qs.filter(name__icontains=search)
|
||||
return qs[(page - 1) * per_page : page * per_page]
|
||||
|
||||
def get_by_id(self, product_id: int):
|
||||
return Product.objects.select_related('category').get(pk=product_id)
|
||||
|
||||
def create(self, data: dict):
|
||||
return Product.objects.create(**data)
|
||||
|
||||
def save(self, product):
|
||||
product.save()
|
||||
return product
|
||||
```
|
||||
|
||||
## Serializer Pattern (DRF)
|
||||
|
||||
```python
|
||||
# apps/products/serializers.py
|
||||
from rest_framework import serializers
|
||||
from .models import Product, Category
|
||||
|
||||
|
||||
class CategorySerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Category
|
||||
fields = ['id', 'name', 'slug']
|
||||
|
||||
|
||||
class ProductListSerializer(serializers.ModelSerializer):
|
||||
category = CategorySerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Product
|
||||
fields = ['id', 'name', 'slug', 'price', 'category', 'is_active']
|
||||
|
||||
|
||||
class ProductDetailSerializer(serializers.ModelSerializer):
|
||||
category = CategorySerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Product
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class ProductCreateSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Product
|
||||
fields = ['name', 'description', 'price', 'category', 'is_active']
|
||||
|
||||
def validate_price(self, value):
|
||||
if value <= 0:
|
||||
raise serializers.ValidationError('Price must be positive')
|
||||
return value
|
||||
```
|
||||
|
||||
## View Pattern (Thin Views)
|
||||
|
||||
```python
|
||||
# apps/products/views.py
|
||||
from rest_framework import viewsets, filters, status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from .models import Product
|
||||
from .serializers import ProductListSerializer, ProductDetailSerializer, ProductCreateSerializer
|
||||
from .services import ProductService
|
||||
from .repositories import ProductRepository
|
||||
|
||||
|
||||
class ProductViewSet(viewsets.ModelViewSet):
|
||||
queryset = Product.objects.filter(is_active=True).select_related('category')
|
||||
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
|
||||
search_fields = ['name', 'description']
|
||||
ordering_fields = ['price', 'created_at']
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == 'list':
|
||||
return ProductListSerializer
|
||||
if self.action in ('create', 'update', 'partial_update'):
|
||||
return ProductCreateSerializer
|
||||
return ProductDetailSerializer
|
||||
|
||||
def get_service(self):
|
||||
return ProductService(ProductRepository())
|
||||
|
||||
def create(self, request):
|
||||
service = self.get_service()
|
||||
product = service.create(request.data)
|
||||
return Response(
|
||||
ProductDetailSerializer(product).data,
|
||||
status=status.HTTP_201_CREATED,
|
||||
)
|
||||
```
|
||||
|
||||
## Authentication (DRF + JWT)
|
||||
|
||||
```python
|
||||
# config/settings/base.py
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||
],
|
||||
'DEFAULT_PERMISSION_CLASSES': [
|
||||
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
|
||||
],
|
||||
'DEFAULT_PAGINATION_CLASS': 'shared.pagination.StandardPagination',
|
||||
'PAGE_SIZE': 20,
|
||||
}
|
||||
```
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Fat models, thin views, services for business logic
|
||||
- [ ] Repository pattern for data access
|
||||
- [ ] DRF serializers for input validation and output transformation
|
||||
- [ ] Select_related / prefetch_related to prevent N+1
|
||||
- [ ] JWT (SimpleJWT) for API authentication
|
||||
- [ ] Environment-specific settings (base/development/production)
|
||||
- [ ] Django admin for data management
|
||||
- [ ] pytest-django for testing
|
||||
- [ ] Celery for async tasks
|
||||
- [ ] `python manage.py check --deploy` before release
|
||||
318
.kilo/skills/python-fastapi-patterns/SKILL.md
Normal file
318
.kilo/skills/python-fastapi-patterns/SKILL.md
Normal file
@@ -0,0 +1,318 @@
|
||||
---
|
||||
name: python-fastapi-patterns
|
||||
description: FastAPI patterns — async routes, dependency injection, Pydantic, SQLAlchemy, Alembic, background tasks
|
||||
---
|
||||
|
||||
# Python FastAPI Patterns
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
app/
|
||||
├── main.py # FastAPI app
|
||||
├── config.py # Settings (pydantic-settings)
|
||||
├── database.py # Database session
|
||||
├── dependencies.py # Shared dependencies (auth, db, pagination)
|
||||
├── models/ # SQLAlchemy models
|
||||
│ ├── product.py
|
||||
│ ├── order.py
|
||||
│ └── user.py
|
||||
├── schemas/ # Pydantic schemas
|
||||
│ ├── product.py
|
||||
│ ├── order.py
|
||||
│ └── auth.py
|
||||
├── routers/ # API routers
|
||||
│ ├── products.py
|
||||
│ ├── orders.py
|
||||
│ ├── auth.py
|
||||
│ └── admin.py
|
||||
├── services/ # Business logic
|
||||
│ ├── product_service.py
|
||||
│ ├── order_service.py
|
||||
│ └── auth_service.py
|
||||
├── repositories/ # Data access
|
||||
│ ├── product_repository.py
|
||||
│ └── order_repository.py
|
||||
├── middleware/
|
||||
│ ├── auth.py
|
||||
│ └── logging.py
|
||||
├── tasks/ # Background tasks
|
||||
│ └── notifications.py
|
||||
├── exceptions.py # Custom exceptions
|
||||
├── migrations/ # Alembic migrations
|
||||
│ ├── env.py
|
||||
│ └── versions/
|
||||
└── tests/
|
||||
├── conftest.py
|
||||
├── test_products.py
|
||||
└── test_auth.py
|
||||
```
|
||||
|
||||
## App Setup
|
||||
|
||||
```python
|
||||
# app/main.py
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from app.config import settings
|
||||
from app.routers import products, orders, auth
|
||||
|
||||
app = FastAPI(
|
||||
title=settings.APP_NAME,
|
||||
version=settings.APP_VERSION,
|
||||
docs_url='/api/docs',
|
||||
redoc_url='/api/redoc',
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=settings.ALLOWED_ORIGINS,
|
||||
allow_credentials=True,
|
||||
allow_methods=['*'],
|
||||
allow_headers=['*'],
|
||||
)
|
||||
|
||||
app.include_router(products.router, prefix='/api/v1', tags=['products'])
|
||||
app.include_router(orders.router, prefix='/api/v1', tags=['orders'])
|
||||
app.include_router(auth.router, prefix='/api/v1/auth', tags=['auth'])
|
||||
|
||||
|
||||
@app.get('/health')
|
||||
async def health_check():
|
||||
return {'status': 'ok'}
|
||||
```
|
||||
|
||||
## Config
|
||||
|
||||
```python
|
||||
# app/config.py
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
APP_NAME: str = 'My API'
|
||||
APP_VERSION: str = '1.0.0'
|
||||
DATABASE_URL: str = 'postgresql+asyncpg://user:pass@localhost/db'
|
||||
SECRET_KEY: str = 'change-me-in-production'
|
||||
ALLOWED_ORIGINS: list[str] = ['http://localhost:3000']
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
|
||||
|
||||
model_config = {'env_file': '.env', 'extra': 'ignore'}
|
||||
|
||||
|
||||
settings = Settings()
|
||||
```
|
||||
|
||||
## Database
|
||||
|
||||
```python
|
||||
# app/database.py
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
|
||||
from app.config import settings
|
||||
|
||||
engine = create_async_engine(settings.DATABASE_URL, echo=False)
|
||||
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)
|
||||
|
||||
|
||||
async def get_db() -> AsyncSession:
|
||||
async with AsyncSessionLocal() as session:
|
||||
yield session
|
||||
```
|
||||
|
||||
## Pydantic Schemas
|
||||
|
||||
```python
|
||||
# app/schemas/product.py
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ProductBase(BaseModel):
|
||||
name: str = Field(min_length=1, max_length=255)
|
||||
price: float = Field(gt=0)
|
||||
category_id: int
|
||||
|
||||
|
||||
class ProductCreate(ProductBase):
|
||||
description: str | None = None
|
||||
|
||||
|
||||
class ProductUpdate(BaseModel):
|
||||
name: str | None = Field(None, min_length=1, max_length=255)
|
||||
price: float | None = Field(None, gt=0)
|
||||
|
||||
|
||||
class ProductResponse(ProductBase):
|
||||
id: int
|
||||
description: str | None
|
||||
slug: str
|
||||
|
||||
model_config = {'from_attributes': True}
|
||||
|
||||
|
||||
class ProductList(BaseModel):
|
||||
data: list[ProductResponse]
|
||||
total: int
|
||||
page: int
|
||||
pages: int
|
||||
```
|
||||
|
||||
## Repository Pattern
|
||||
|
||||
```python
|
||||
# app/repositories/product_repository.py
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.models.product import Product
|
||||
|
||||
|
||||
class ProductRepository:
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
|
||||
async def list(self, page=1, per_page=20, category_id=None, search=None):
|
||||
query = select(Product).where(Product.is_active.is_(True))
|
||||
if category_id:
|
||||
query = query.where(Product.category_id == category_id)
|
||||
if search:
|
||||
query = query.where(Product.name.ilike(f'%{search}%'))
|
||||
|
||||
total = await self.db.scalar(select(func.count()).select_from(query.subquery()))
|
||||
items = await self.db.scalars(
|
||||
query.order_by(Product.created_at.desc())
|
||||
.offset((page - 1) * per_page)
|
||||
.limit(per_page)
|
||||
)
|
||||
return items.all(), total
|
||||
|
||||
async def get_by_id(self, product_id: int):
|
||||
return await self.db.get(Product, product_id)
|
||||
|
||||
async def create(self, data: dict):
|
||||
product = Product(**data)
|
||||
self.db.add(product)
|
||||
await self.db.commit()
|
||||
await self.db.refresh(product)
|
||||
return product
|
||||
```
|
||||
|
||||
## Router Pattern (Thin)
|
||||
|
||||
```python
|
||||
# app/routers/products.py
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.database import get_db
|
||||
from app.schemas.product import ProductCreate, ProductResponse, ProductList
|
||||
from app.services.product_service import ProductService
|
||||
from app.dependencies import PaginationParams
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def get_service(db: AsyncSession = Depends(get_db)) -> ProductService:
|
||||
return ProductService(db)
|
||||
|
||||
|
||||
@router.get('/products', response_model=ProductList)
|
||||
async def list_products(
|
||||
pagination: PaginationParams = Depends(),
|
||||
category_id: int | None = None,
|
||||
search: str | None = None,
|
||||
service: ProductService = Depends(get_service),
|
||||
):
|
||||
return await service.list(
|
||||
page=pagination.page,
|
||||
per_page=pagination.per_page,
|
||||
category_id=category_id,
|
||||
search=search,
|
||||
)
|
||||
|
||||
|
||||
@router.post('/products', response_model=ProductResponse, status_code=201)
|
||||
async def create_product(
|
||||
data: ProductCreate,
|
||||
service: ProductService = Depends(get_service),
|
||||
):
|
||||
return await service.create(data.model_dump())
|
||||
|
||||
|
||||
@router.get('/products/{product_id}', response_model=ProductResponse)
|
||||
async def get_product(product_id: int, service: ProductService = Depends(get_service)):
|
||||
product = await service.get(product_id)
|
||||
if not product:
|
||||
raise HTTPException(status_code=404, detail='Product not found')
|
||||
return product
|
||||
```
|
||||
|
||||
## Authentication (JWT)
|
||||
|
||||
```python
|
||||
# app/services/auth_service.py
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from jose import jwt, JWTError
|
||||
from passlib.context import CryptContext
|
||||
from app.config import settings
|
||||
|
||||
pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
|
||||
|
||||
|
||||
class AuthService:
|
||||
@staticmethod
|
||||
def hash_password(password: str) -> str:
|
||||
return pwd_context.hash(password)
|
||||
|
||||
@staticmethod
|
||||
def verify_password(plain: str, hashed: str) -> bool:
|
||||
return pwd_context.verify(plain, hashed)
|
||||
|
||||
@staticmethod
|
||||
def create_access_token(user_id: int) -> str:
|
||||
expire = datetime.now(timezone.utc) + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
return jwt.encode({'sub': str(user_id), 'exp': expire}, settings.SECRET_KEY)
|
||||
|
||||
@staticmethod
|
||||
def decode_token(token: str) -> dict:
|
||||
try:
|
||||
return jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
|
||||
except JWTError:
|
||||
raise ValueError('Invalid token')
|
||||
```
|
||||
|
||||
## Dependencies (Auth + Pagination)
|
||||
|
||||
```python
|
||||
# app/dependencies.py
|
||||
from fastapi import Depends, HTTPException, Header
|
||||
from app.services.auth_service import AuthService
|
||||
|
||||
|
||||
class PaginationParams:
|
||||
def __init__(self, page: int = 1, per_page: int = 20):
|
||||
self.page = max(1, page)
|
||||
self.per_page = min(100, max(1, per_page))
|
||||
|
||||
|
||||
async def get_current_user(authorization: str = Header(...)):
|
||||
if not authorization.startswith('Bearer '):
|
||||
raise HTTPException(status_code=401, detail='Invalid token format')
|
||||
token = authorization[7:]
|
||||
try:
|
||||
payload = AuthService.decode_token(token)
|
||||
return int(payload['sub'])
|
||||
except (ValueError, KeyError):
|
||||
raise HTTPException(status_code=401, detail='Invalid or expired token')
|
||||
```
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Async everywhere (routes, db, external calls)
|
||||
- [ ] Dependency injection for services and auth
|
||||
- [ ] Pydantic v2 schemas with `model_config = {'from_attributes': True}`
|
||||
- [ ] Repository pattern for data access
|
||||
- [ ] Service layer for business logic
|
||||
- [ ] Alembic for database migrations
|
||||
- [ ] JWT for authentication
|
||||
- [ ] CORS properly configured
|
||||
- [ ] pytest-asyncio for async tests
|
||||
- [ ] `alembic upgrade head` before server start
|
||||
- [ ] Health check endpoint for monitoring
|
||||
277
.kilo/skills/react-patterns/SKILL.md
Normal file
277
.kilo/skills/react-patterns/SKILL.md
Normal file
@@ -0,0 +1,277 @@
|
||||
---
|
||||
name: react-patterns
|
||||
description: React 18+ patterns — Hooks, composition, context, suspense, concurrent features, component architecture
|
||||
---
|
||||
|
||||
# React Patterns
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── app/ or pages/ # Next.js or Vite pages
|
||||
├── components/
|
||||
│ ├── ui/ # Base UI primitives
|
||||
│ │ ├── Button.tsx
|
||||
│ │ ├── Input.tsx
|
||||
│ │ ├── Modal.tsx
|
||||
│ │ └── Card.tsx
|
||||
│ ├── forms/ # Form components
|
||||
│ │ ├── LoginForm.tsx
|
||||
│ │ └── ProductForm.tsx
|
||||
│ └── features/ # Feature components
|
||||
│ ├── product/
|
||||
│ ├── cart/
|
||||
│ └── auth/
|
||||
├── hooks/ # Custom hooks
|
||||
│ ├── useAuth.ts
|
||||
│ ├── useCart.ts
|
||||
│ ├── useDebounce.ts
|
||||
│ └── useApi.ts
|
||||
├── context/ # React Context providers
|
||||
│ ├── AuthContext.tsx
|
||||
│ └── CartContext.tsx
|
||||
├── services/ # API services
|
||||
│ ├── api.ts # Axios/fetch instance
|
||||
│ ├── authService.ts
|
||||
│ └── productService.ts
|
||||
├── types/ # TypeScript types
|
||||
│ └── index.ts
|
||||
├── utils/ # Utilities
|
||||
│ ├── format.ts
|
||||
│ └── validation.ts
|
||||
└── lib/ # Third-party config
|
||||
├── queryClient.ts # TanStack Query
|
||||
└── supabase.ts
|
||||
```
|
||||
|
||||
## Component Pattern
|
||||
|
||||
```tsx
|
||||
// components/product/ProductCard.tsx
|
||||
import { useState } from 'react';
|
||||
import { useCart } from '@/hooks/useCart';
|
||||
|
||||
interface ProductCardProps {
|
||||
product: Product;
|
||||
showAddToCart?: boolean;
|
||||
onAddToCart?: (productId: string) => void;
|
||||
}
|
||||
|
||||
export function ProductCard({ product, showAddToCart = true, onAddToCart }: ProductCardProps) {
|
||||
const [isAdding, setIsAdding] = useState(false);
|
||||
const { addItem } = useCart();
|
||||
|
||||
const handleAdd = async () => {
|
||||
setIsAdding(true);
|
||||
await addItem(product.id, 1);
|
||||
onAddToCart?.(product.id);
|
||||
setIsAdding(false);
|
||||
};
|
||||
|
||||
const formattedPrice = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency', currency: 'USD',
|
||||
}).format(product.price);
|
||||
|
||||
return (
|
||||
<div className="border rounded-lg p-4 hover:shadow-lg transition-shadow">
|
||||
<a href={`/products/${product.id}`}>
|
||||
<img src={product.image} alt={product.name} className="w-full h-48 object-cover rounded" />
|
||||
<h3 className="mt-2 font-semibold">{product.name}</h3>
|
||||
<p className="text-gray-600">{formattedPrice}</p>
|
||||
</a>
|
||||
|
||||
{showAddToCart && (
|
||||
<button
|
||||
onClick={handleAdd}
|
||||
disabled={isAdding}
|
||||
className="mt-2 w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
{isAdding ? 'Adding...' : 'Add to Cart'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Custom Hook Pattern
|
||||
|
||||
```ts
|
||||
// hooks/useCart.ts
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { cartService } from '@/services/cartService';
|
||||
|
||||
export function useCart() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data: cart } = useQuery({
|
||||
queryKey: ['cart'],
|
||||
queryFn: cartService.get,
|
||||
});
|
||||
|
||||
const addItemMutation = useMutation({
|
||||
mutationFn: cartService.addItem,
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['cart'] }),
|
||||
});
|
||||
|
||||
const removeItemMutation = useMutation({
|
||||
mutationFn: cartService.removeItem,
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['cart'] }),
|
||||
});
|
||||
|
||||
const total = cart?.items?.reduce((sum, item) => sum + item.price * item.quantity, 0) ?? 0;
|
||||
const count = cart?.items?.reduce((sum, item) => sum + item.quantity, 0) ?? 0;
|
||||
|
||||
return {
|
||||
cart,
|
||||
total,
|
||||
count,
|
||||
addItem: addItemMutation.mutateAsync,
|
||||
removeItem: removeItemMutation.mutateAsync,
|
||||
isAdding: addItemMutation.isPending,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Context Pattern
|
||||
|
||||
```tsx
|
||||
// context/AuthContext.tsx
|
||||
import { createContext, useContext, useState, useCallback, type ReactNode } from 'react';
|
||||
|
||||
interface AuthContextType {
|
||||
user: User | null;
|
||||
isAuthenticated: boolean;
|
||||
login: (email: string, password: string) => Promise<void>;
|
||||
logout: () => void;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType | null>(null);
|
||||
|
||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
|
||||
const login = useCallback(async (email: string, password: string) => {
|
||||
const response = await authService.login({ email, password });
|
||||
setUser(response.user);
|
||||
localStorage.setItem('token', response.token);
|
||||
}, []);
|
||||
|
||||
const logout = useCallback(() => {
|
||||
setUser(null);
|
||||
localStorage.removeItem('token');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, isAuthenticated: !!user, login, logout }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useAuth() {
|
||||
const context = useContext(AuthContext);
|
||||
if (!context) throw new Error('useAuth must be used within AuthProvider');
|
||||
return context;
|
||||
}
|
||||
```
|
||||
|
||||
## API Service Pattern
|
||||
|
||||
```ts
|
||||
// services/api.ts
|
||||
import axios from 'axios';
|
||||
|
||||
export const api = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_URL || '/api',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
|
||||
api.interceptors.request.use((config) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) config.headers.Authorization = `Bearer ${token}`;
|
||||
return config;
|
||||
});
|
||||
|
||||
api.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
localStorage.removeItem('token');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// services/productService.ts
|
||||
import { api } from './api';
|
||||
|
||||
export const productService = {
|
||||
list: (params?: object) => api.get('/products', { params }).then((r) => r.data),
|
||||
get: (id: string) => api.get(`/products/${id}`).then((r) => r.data),
|
||||
create: (data: CreateProductDTO) => api.post('/products', data).then((r) => r.data),
|
||||
update: (id: string, data: UpdateProductDTO) => api.put(`/products/${id}`, data).then((r) => r.data),
|
||||
delete: (id: string) => api.delete(`/products/${id}`).then((r) => r.data),
|
||||
};
|
||||
```
|
||||
|
||||
## Form Pattern (React Hook Form + Zod)
|
||||
|
||||
```tsx
|
||||
// components/forms/ProductForm.tsx
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(1, 'Name is required').max(255),
|
||||
price: z.number().positive('Price must be positive'),
|
||||
categoryId: z.string().min(1, 'Category is required'),
|
||||
description: z.string().optional(),
|
||||
});
|
||||
|
||||
type ProductFormData = z.infer<typeof schema>;
|
||||
|
||||
export function ProductForm({ onSubmit }: { onSubmit: (data: ProductFormData) => Promise<void> }) {
|
||||
const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<ProductFormData>({
|
||||
resolver: zodResolver(schema),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||
<div>
|
||||
<label>Name</label>
|
||||
<input {...register('name')} className="border rounded px-3 py-2 w-full" />
|
||||
{errors.name && <p className="text-red-500 text-sm">{errors.name.message}</p>}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Price</label>
|
||||
<input type="number" step="0.01" {...register('price', { valueAsNumber: true })} className="border rounded px-3 py-2 w-full" />
|
||||
{errors.price && <p className="text-red-500 text-sm">{errors.price.message}</p>}
|
||||
</div>
|
||||
|
||||
<button type="submit" disabled={isSubmitting} className="bg-blue-600 text-white px-4 py-2 rounded">
|
||||
{isSubmitting ? 'Saving...' : 'Save Product'}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Functional components with hooks (no class components)
|
||||
- [ ] TypeScript with strict mode
|
||||
- [ ] `interface` for props, `type` for unions
|
||||
- [ ] Custom hooks for reusable logic (data fetching, auth, forms)
|
||||
- [ ] React Hook Form + Zod for form validation
|
||||
- [ ] TanStack Query for server state (not useState for API data)
|
||||
- [ ] Context only for global state (auth, theme)
|
||||
- [ ] React.memo for expensive renders only
|
||||
- [ ] Error boundaries for crash recovery
|
||||
- [ ] Suspense for loading states
|
||||
- [ ] useCallback/useMemo only when needed (not by default)
|
||||
- [ ] Clean up effects (return cleanup function)
|
||||
327
.kilo/skills/vue-nuxt-patterns/SKILL.md
Normal file
327
.kilo/skills/vue-nuxt-patterns/SKILL.md
Normal file
@@ -0,0 +1,327 @@
|
||||
---
|
||||
name: vue-nuxt-patterns
|
||||
description: Vue 3 + Nuxt 3 patterns — Composition API, Pinia, SSR/ISR, auto-imports, middleware, Nitro server
|
||||
---
|
||||
|
||||
# Vue/Nuxt Patterns
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/ or ~/
|
||||
├── app.vue # Root app
|
||||
├── layouts/
|
||||
│ ├── default.vue # Main layout (header, footer)
|
||||
│ └── admin.vue # Admin layout (sidebar)
|
||||
├── pages/
|
||||
│ ├── index.vue # Home
|
||||
│ ├── products/
|
||||
│ │ ├── index.vue # Product list
|
||||
│ │ └── [id].vue # Product detail
|
||||
│ ├── cart.vue
|
||||
│ ├── checkout.vue
|
||||
│ └── admin/
|
||||
│ ├── index.vue
|
||||
│ └── products.vue
|
||||
├── components/
|
||||
│ ├── ui/ # Base UI (Button, Input, Modal)
|
||||
│ ├── product/
|
||||
│ │ ├── ProductCard.vue
|
||||
│ │ ├── ProductGrid.vue
|
||||
│ │ └── ProductFilters.vue
|
||||
│ ├── cart/
|
||||
│ │ ├── CartItem.vue
|
||||
│ │ └── CartSummary.vue
|
||||
│ └── layout/
|
||||
│ ├── AppHeader.vue
|
||||
│ └── AppSidebar.vue
|
||||
├── composables/ # Auto-imported
|
||||
│ ├── useCart.ts
|
||||
│ ├── useAuth.ts
|
||||
│ ├── useProducts.ts
|
||||
│ └── useApi.ts
|
||||
├── stores/ # Pinia stores
|
||||
│ ├── cart.ts
|
||||
│ ├── auth.ts
|
||||
│ └── products.ts
|
||||
├── server/
|
||||
│ ├── api/
|
||||
│ │ ├── products/index.get.ts
|
||||
│ │ ├── products/[id].get.ts
|
||||
│ │ ├── products/index.post.ts
|
||||
│ │ ├── cart/index.get.ts
|
||||
│ │ └── auth/login.post.ts
|
||||
│ ├── middleware/
|
||||
│ │ └── auth.ts
|
||||
│ └── utils/
|
||||
│ └── db.ts
|
||||
├── middleware/ # Route middleware
|
||||
│ └── auth.ts
|
||||
├── types/
|
||||
│ └── index.ts
|
||||
├── utils/
|
||||
│ └── format.ts
|
||||
└── nuxt.config.ts
|
||||
```
|
||||
|
||||
## Page Component Pattern
|
||||
|
||||
```vue
|
||||
<!-- pages/products/index.vue -->
|
||||
<script setup lang="ts">
|
||||
const { data: products, pending, error } = await useFetch('/api/products', {
|
||||
query: { page: 1, limit: 20 },
|
||||
default: () => [],
|
||||
})
|
||||
|
||||
const filters = ref({ category: null, search: '' })
|
||||
|
||||
const filtered = computed(() =>
|
||||
products.value.filter((p) => {
|
||||
if (filters.value.category && p.categoryId !== filters.value.category) return false
|
||||
if (filters.value.search && !p.name.toLowerCase().includes(filters.value.search.toLowerCase())) return false
|
||||
return true
|
||||
})
|
||||
)
|
||||
|
||||
useHead({ title: 'Products' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto px-4">
|
||||
<ProductFilters
|
||||
v-model:category="filters.category"
|
||||
v-model:search="filters.search"
|
||||
/>
|
||||
|
||||
<ProductGrid :products="filtered" :loading="pending" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Composable Pattern
|
||||
|
||||
```ts
|
||||
// composables/useCart.ts
|
||||
export const useCart = () => {
|
||||
const cart = useState<CartItem[]>('cart', () => [])
|
||||
const items = useCookie<CartItem[]>('cart-items', { default: () => [] })
|
||||
|
||||
const total = computed(() =>
|
||||
items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
|
||||
)
|
||||
|
||||
const count = computed(() =>
|
||||
items.value.reduce((sum, item) => sum + item.quantity, 0)
|
||||
)
|
||||
|
||||
async function addItem(productId: string, quantity = 1) {
|
||||
const { data, error } = await useFetch('/api/cart/items', {
|
||||
method: 'POST',
|
||||
body: { productId, quantity },
|
||||
})
|
||||
if (!error.value) {
|
||||
items.value = data.value?.items || items.value
|
||||
}
|
||||
}
|
||||
|
||||
async function removeItem(itemId: string) {
|
||||
await useFetch(`/api/cart/items/${itemId}`, { method: 'DELETE' })
|
||||
items.value = items.value.filter((i) => i.id !== itemId)
|
||||
}
|
||||
|
||||
return { items, total, count, addItem, removeItem }
|
||||
}
|
||||
```
|
||||
|
||||
## Pinia Store
|
||||
|
||||
```ts
|
||||
// stores/auth.ts
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const user = ref<User | null>(null)
|
||||
const token = useCookie('auth-token')
|
||||
|
||||
const isAuthenticated = computed(() => !!token.value)
|
||||
|
||||
async function login(email: string, password: string) {
|
||||
const { data, error } = await useFetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
body: { email, password },
|
||||
})
|
||||
if (!error.value && data.value) {
|
||||
token.value = data.value.token
|
||||
user.value = data.value.user
|
||||
}
|
||||
return { data, error }
|
||||
}
|
||||
|
||||
function logout() {
|
||||
token.value = null
|
||||
user.value = null
|
||||
navigateTo('/login')
|
||||
}
|
||||
|
||||
return { user, token, isAuthenticated, login, logout }
|
||||
})
|
||||
```
|
||||
|
||||
## Server API (Nitro)
|
||||
|
||||
```ts
|
||||
// server/api/products/index.get.ts
|
||||
import { defineEventHandler, getQuery } from 'h3'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const { page = '1', limit = '20', category, search } = getQuery(event)
|
||||
|
||||
const where: any = {}
|
||||
if (category) where.categoryId = category
|
||||
if (search) where.name = { contains: search, mode: 'insensitive' }
|
||||
|
||||
const [products, total] = await Promise.all([
|
||||
prisma.product.findMany({
|
||||
where,
|
||||
skip: (Number(page) - 1) * Number(limit),
|
||||
take: Number(limit),
|
||||
include: { category: true },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.product.count({ where }),
|
||||
])
|
||||
|
||||
return {
|
||||
data: products,
|
||||
meta: { page: Number(page), limit: Number(limit), total, pages: Math.ceil(total / Number(limit)) },
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
```ts
|
||||
// server/api/products/index.post.ts
|
||||
import { defineEventHandler, readBody, createError } from 'h3'
|
||||
import { z } from 'zod'
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(1).max(255),
|
||||
price: z.number().positive(),
|
||||
categoryId: z.string().cuid(),
|
||||
})
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event)
|
||||
const parsed = schema.safeParse(body)
|
||||
|
||||
if (!parsed.success) {
|
||||
throw createError({ statusCode: 422, message: parsed.error.flatten() })
|
||||
}
|
||||
|
||||
const product = await prisma.product.create({ data: parsed.data })
|
||||
return product
|
||||
})
|
||||
```
|
||||
|
||||
## Route Middleware
|
||||
|
||||
```ts
|
||||
// middleware/auth.ts
|
||||
export default defineNuxtRouteMiddleware((to) => {
|
||||
const { isAuthenticated } = useAuthStore()
|
||||
if (!isAuthenticated && to.path.startsWith('/admin')) {
|
||||
return navigateTo('/login')
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Component Pattern (Composition API)
|
||||
|
||||
```vue
|
||||
<!-- components/product/ProductCard.vue -->
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
product: Product
|
||||
showAddToCart?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
showAddToCart: true,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
addToCart: [productId: string]
|
||||
}>()
|
||||
|
||||
const { addItem } = useCart()
|
||||
const isAdding = ref(false)
|
||||
|
||||
async function handleAdd() {
|
||||
isAdding.value = true
|
||||
await addItem(props.product.id)
|
||||
isAdding.value = false
|
||||
emit('addToCart', props.product.id)
|
||||
}
|
||||
|
||||
const formattedPrice = computed(() =>
|
||||
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(props.product.price)
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="border rounded-lg p-4 hover:shadow-lg transition-shadow">
|
||||
<NuxtLink :to="`/products/${product.id}`">
|
||||
<img :src="product.image" :alt="product.name" class="w-full h-48 object-cover rounded" />
|
||||
<h3 class="mt-2 font-semibold">{{ product.name }}</h3>
|
||||
<p class="text-gray-600">{{ formattedPrice }}</p>
|
||||
</NuxtLink>
|
||||
|
||||
<button
|
||||
v-if="showAddToCart"
|
||||
:disabled="isAdding"
|
||||
@click="handleAdd"
|
||||
class="mt-2 w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
{{ isAdding ? 'Adding...' : 'Add to Cart' }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Nuxt Config
|
||||
|
||||
```ts
|
||||
// nuxt.config.ts
|
||||
export default defineNuxtConfig({
|
||||
modules: [
|
||||
'@pinia/nuxt',
|
||||
'@nuxtjs/tailwindcss',
|
||||
'@nuxt/image',
|
||||
],
|
||||
runtimeConfig: {
|
||||
public: { apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api' },
|
||||
private: { dbUrl: process.env.DATABASE_URL },
|
||||
},
|
||||
app: {
|
||||
head: {
|
||||
title: 'My App',
|
||||
meta: [{ name: 'description', content: 'My Nuxt app' }],
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Composition API with `<script setup lang="ts">`
|
||||
- [ ] Auto-imports (composables, components, utils)
|
||||
- [ ] Pinia for state (not Vuex)
|
||||
- [ ] Server API routes (Nitro) for backend logic
|
||||
- [ ] `useFetch` / `useLazyFetch` for data fetching
|
||||
- [ ] `useState` for shared client state
|
||||
- [ ] `useCookie` for persistent state
|
||||
- [ ] Route middleware for auth checks
|
||||
- [ ] `useHead` for SEO meta
|
||||
- [ ] `defineNuxtConfig` with modules
|
||||
- [ ] SSR-friendly code (no `window` in composables)
|
||||
- [ ] `useAsyncData` for server-side data
|
||||
@@ -16,6 +16,12 @@ Agent: Runs full pipeline for issue #42 with Gitea logging
|
||||
| Command | Description | Usage |
|
||||
|---------|-------------|-------|
|
||||
| `/pipeline <issue>` | Run full agent pipeline for issue | `/pipeline 42` |
|
||||
| `/nextjs` | Next.js 14+ full-stack app pipeline | `/nextjs my-app` |
|
||||
| `/vue` | Vue/Nuxt 3 full-stack app pipeline | `/vue my-app` |
|
||||
| `/laravel` | Laravel full-stack app pipeline | `/laravel my-app` |
|
||||
| `/wordpress` | WordPress plugin/site pipeline | `/wordpress my-plugin` |
|
||||
| `/feature` | Feature development pipeline | `/feature` |
|
||||
| `/commerce` | E-commerce site pipeline | `/commerce` |
|
||||
| `/status <issue>` | Check pipeline status for issue | `/status 42` |
|
||||
| `/evolve` | Run evolution cycle with fitness scoring | `/evolve --issue 42` |
|
||||
| `/evaluate <issue>` | Generate performance report | `/evaluate 42` |
|
||||
@@ -41,9 +47,10 @@ These agents are invoked automatically by `/pipeline` or manually via `@mention`
|
||||
| `@system-analyst` | Designs specifications | glm-5.1 | thinking | sdet-engineer, orchestrator |
|
||||
| `@sdet-engineer` | Writes tests (TDD) | qwen3-coder:480b | thinking | lead-developer, orchestrator |
|
||||
| `@lead-developer` | Implements code | qwen3-coder:480b | thinking | code-skeptic, orchestrator |
|
||||
| `@frontend-developer` | UI implementation | qwen3-coder:480b | — | code-skeptic, orchestrator |
|
||||
| `@frontend-developer` | UI (Next.js, Vue/Nuxt, React) | qwen3-coder:480b | — | code-skeptic, visual-tester, orchestrator |
|
||||
| `@backend-developer` | Node.js/Express/APIs | qwen3-coder:480b | — | code-skeptic, orchestrator |
|
||||
| `@php-developer` | PHP/Laravel/Symfony/WordPress | qwen3-coder:480b | thinking | code-skeptic, security-auditor, orchestrator |
|
||||
| `@python-developer` | Python/Django/FastAPI | qwen3-coder:480b | thinking | code-skeptic, security-auditor, orchestrator |
|
||||
| `@go-developer` | Go backend services | qwen3-coder:480b | — | code-skeptic, orchestrator |
|
||||
| `@flutter-developer` | Flutter mobile apps | qwen3-coder:480b | — | code-skeptic, orchestrator |
|
||||
|
||||
|
||||
31
README.md
31
README.md
@@ -1,6 +1,8 @@
|
||||
# APAW — Automatic Programmers Agent Workflow
|
||||
|
||||
**Self-Improving Agent Pipeline** — автономная система из 29+ специализированных ИИ-агентов с автоматической эволюцией промптов, мониторингом выполнения и модульной архитектурой.
|
||||
**Self-Improving Agent Pipeline** — автономная система из 30+ специализированных ИИ-агентов с автоматической эволюцией промптов, мониторингом выполнения и модульной архитектурой.
|
||||
|
||||
Поддерживаемые стеки: **PHP/Laravel/Symfony/WordPress**, **Next.js**, **Vue/Nuxt**, **React**, **Python/Django/FastAPI**, **Go**, **Flutter**, **Node.js/Express**.
|
||||
|
||||
---
|
||||
|
||||
@@ -9,8 +11,8 @@
|
||||
```
|
||||
APAW/
|
||||
├── .kilo/ # KiloCode конфигурация
|
||||
│ ├── agents/ # 29 агентов (YAML frontmatter)
|
||||
│ ├── commands/ # Workflow команды (/pipeline, /laravel, /wordpress, etc.)
|
||||
│ ├── agents/ # 30 агентов (YAML frontmatter)
|
||||
│ ├── commands/ # Workflow команды (/pipeline, /laravel, /nextjs, /vue, etc.)
|
||||
│ ├── rules/ # Правила кодирования (атомарные задачи, модульность, токены)
|
||||
│ ├── skills/ # Специализированные навыки (PHP, Go, Node, Docker, Gitea)
|
||||
│ ├── shared/ # Общие модули (gitea-api, gitea-commenting, self-evolution)
|
||||
@@ -74,7 +76,7 @@ bun run agent:stats --project UniqueSoft/my-shop
|
||||
|
||||
---
|
||||
|
||||
## Команда агентов (29+)
|
||||
## Команда агентов (30+)
|
||||
|
||||
### Планирование и Анализ
|
||||
|
||||
@@ -95,6 +97,7 @@ bun run agent:stats --project UniqueSoft/my-shop
|
||||
| `@frontend-developer` | Qwen3-Coder 480B | UI компоненты |
|
||||
| `@backend-developer` | Qwen3-Coder 480B | Node.js/Express APIs |
|
||||
| `@php-developer` | Qwen3-Coder 480B | PHP/Laravel/Symfony/WordPress |
|
||||
| `@python-developer` | Qwen3-Coder 480B | Python/Django/FastAPI |
|
||||
| `@go-developer` | Qwen3-Coder 480B | Go/Gin/Echo APIs |
|
||||
| `@flutter-developer` | Qwen3-Coder 480B | Flutter mobile apps |
|
||||
| `@devops-engineer` | Nemotron-3-Super | Docker, K8s, CI/CD |
|
||||
@@ -219,6 +222,11 @@ Subagent модели определены в `.md` файлах агентов.
|
||||
- `php_web_development` → `php-developer`
|
||||
- `laravel_development` → `php-developer`
|
||||
- `wordpress_development` → `php-developer`
|
||||
- `python_web_development` → `python-developer`
|
||||
- `django_development` → `python-developer`
|
||||
- `fastapi_development` → `python-developer`
|
||||
- `nextjs_development` → `frontend-developer`
|
||||
- `vue_nuxt_development` → `frontend-developer`
|
||||
- `security` → `security-auditor`
|
||||
- и т.д.
|
||||
|
||||
@@ -258,6 +266,15 @@ bun run agent:stats
|
||||
- `php-testing` — PHPUnit, Pest, Dusk
|
||||
- `php-modular-architecture` — Modules, packages, service separation
|
||||
|
||||
### Frontend (Next.js / Vue / React)
|
||||
- `nextjs-patterns` — Next.js 14+ App Router, Server Components, Server Actions, Auth.js
|
||||
- `vue-nuxt-patterns` — Vue 3 / Nuxt 3 Composition API, Pinia, Nitro, SSR
|
||||
- `react-patterns` — React 18+ hooks, Context, TanStack Query, React Hook Form
|
||||
|
||||
### Python
|
||||
- `python-django-patterns` — Django models, DRF, services, repositories
|
||||
- `python-fastapi-patterns` — FastAPI async, Pydantic, SQLAlchemy, dependencies
|
||||
|
||||
### Инфраструктура
|
||||
- `gitea-workflow` — Gitea интеграция (автодетекция проекта)
|
||||
- `gitea-commenting` — Автоматические комментарии
|
||||
@@ -338,7 +355,7 @@ bun run agent:stats:project --project UniqueSoft/my-shop
|
||||
| Runtime | TypeScript / Node.js / Bun |
|
||||
| Agent Runtime | KiloCode VS Code Extension |
|
||||
| Version Control | Gitea + Git Flow |
|
||||
| Languages | TypeScript / PHP / Go / Dart |
|
||||
| Languages | TypeScript / PHP / Python / Go / Dart |
|
||||
| Testing | TDD (Red-Green-Refactor), PHPUnit, Pest |
|
||||
| Containerization | Docker / Docker Compose |
|
||||
|
||||
@@ -347,12 +364,14 @@ bun run agent:stats:project --project UniqueSoft/my-shop
|
||||
## Статус проекта
|
||||
|
||||
✅ Production Ready
|
||||
✅ 29 агентов
|
||||
✅ 30 агентов
|
||||
✅ Self-improving pipeline с fitness scoring
|
||||
✅ Gitea интеграция с автодетекцией проекта
|
||||
✅ Agent Evolution Dashboard
|
||||
✅ Мониторинг выполнения агентов
|
||||
✅ PHP/Laravel/Symfony/WordPress поддержка
|
||||
✅ Next.js / Vue/Nuxt / React поддержка
|
||||
✅ Python/Django/FastAPI поддержка
|
||||
✅ Атомарные задачи и модульная архитектура
|
||||
|
||||
---
|
||||
|
||||
22
STRUCTURE.md
22
STRUCTURE.md
@@ -7,15 +7,18 @@ This document describes the organized structure of the APAW project.
|
||||
```
|
||||
APAW/
|
||||
├── .kilo/ # Kilo Code configuration
|
||||
│ ├── agents/ # 29 agent definitions (YAML frontmatter)
|
||||
│ ├── agents/ # 30 agent definitions (YAML frontmatter)
|
||||
│ │ ├── orchestrator.md # Main dispatcher
|
||||
│ │ ├── php-developer.md # PHP/Laravel/Symfony/WordPress
|
||||
│ │ ├── python-developer.md # Python/Django/FastAPI
|
||||
│ │ ├── lead-developer.md # Primary code writer
|
||||
│ │ ├── code-skeptic.md # Adversarial review
|
||||
│ │ └── ... (25 more)
|
||||
│ ├── commands/ # Slash commands
|
||||
│ │ ├── pipeline.md # Full agent pipeline
|
||||
│ │ ├── laravel.md # Laravel web app pipeline
|
||||
│ │ ├── nextjs.md # Next.js app pipeline
|
||||
│ │ ├── vue.md # Vue/Nuxt app pipeline
|
||||
│ │ ├── wordpress.md # WordPress development pipeline
|
||||
│ │ ├── feature.md # Feature development
|
||||
│ │ ├── commerce.md # E-commerce site
|
||||
@@ -137,7 +140,7 @@ Rules in `.kilo/rules/` are loaded globally for all agents:
|
||||
|
||||
Skills are capability modules agents reference:
|
||||
|
||||
### PHP Development (NEW)
|
||||
### PHP Development
|
||||
|
||||
| Skill | Lines | Purpose |
|
||||
|-------|-------|---------|
|
||||
@@ -148,6 +151,21 @@ Skills are capability modules agents reference:
|
||||
| `php-testing` | 242 | PHPUnit, Pest, Dusk browser tests |
|
||||
| `php-modular-architecture` | 242 | Module separation, interfaces, events |
|
||||
|
||||
### Frontend Frameworks
|
||||
|
||||
| Skill | Lines | Purpose |
|
||||
|-------|-------|---------|
|
||||
| `nextjs-patterns` | 290 | Next.js 14+ App Router, Server Components, Server Actions, Auth.js |
|
||||
| `vue-nuxt-patterns` | 270 | Vue 3 / Nuxt 3 Composition API, Pinia, Nitro, SSR |
|
||||
| `react-patterns` | 240 | React 18+ hooks, Context, TanStack Query, React Hook Form |
|
||||
|
||||
### Python Development
|
||||
|
||||
| Skill | Lines | Purpose |
|
||||
|-------|-------|---------|
|
||||
| `python-django-patterns` | 200 | Django models, DRF, services, repositories |
|
||||
| `python-fastapi-patterns` | 230 | FastAPI async, Pydantic, SQLAlchemy, dependencies |
|
||||
|
||||
### Infrastructure
|
||||
|
||||
| Skill | Purpose |
|
||||
|
||||
Reference in New Issue
Block a user