用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/CleanExpo/ATO --skill data-validation命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Route complex requests to the right specialist agent or chain of agents. This skill acts as the central brain of an agent swarm — it analyses what the user needs, determines which specialist domain(s) are required, and coordinates parallel or sequential agent execution. Use this skill when a request spans multiple domains (e.g., "research competitors and create a pitch deck"), when you need to decide which specialist should handle an ambiguous request, or when a task requires a multi-step pipeline across different skills. Triggers on: multi-step requests, cross-domain tasks, "coordinate", "plan this out", "I need help with multiple things", or any complex request that touches more than one specialist area. Also triggers when the user seems unsure which tool or approach to use.
>-
>-
基于 SOC 职业分类
正在显示 SKILL.md
| id | data-validation |
| name | data-validation |
| type | skill |
| version | 1.0.0 |
| created | 20/03/2026 |
| modified | 20/03/2026 |
| status | active |
| metadata | {"author":"NodeJS-Starter-V1","version":"1.0.0","locale":"en-AU"} |
| description | >- |
| context | fork |
Validation patterns ensuring all data entering the system is validated at boundaries: user input via Zod (frontend), API requests via Pydantic (backend). No unvalidated data crosses a trust boundary.
Defines Zod and Pydantic validation patterns for all data entering the system at trust boundaries. Covers form validation, API request schemas, type-safe contracts, Australian-specific validators (ABN, phone, postcode), and schema composition strategies.
error-taxonomy instead)[User Input] --Zod--> [Frontend] --fetch--> [API] --Pydantic--> [Service Layer]
^ ^
| |
VALIDATE HERE VALIDATE HERE
| Layer | Convention | Example |
|---|---|---|
| Frontend Zod schema | camelCase + Schema suffix | loginFormSchema |
| Frontend inferred type | PascalCase via z.infer | type LoginForm = z.infer<typeof loginFormSchema> |
| Backend Pydantic model | PascalCase + purpose suffix |
DocumentCreateRequest| Shared contract | Same field names both sides | email, password, title |
The project already uses this pattern in apps/web/components/auth/login-form.tsx:
import * as z from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
// 1. Define schema
const loginFormSchema = z.object({
email: z.string().email('Please enter a valid email address'),
password: z.string().min(6, 'Password must be at least 6 characters'),
});
// 2. Infer type (never define manually)
type LoginForm = z.infer<typeof loginFormSchema>;
// 3. Use with react-hook-form
const form = useForm<LoginForm>({
resolver: zodResolver(loginFormSchema),
defaultValues: { email: '', password: '' },
});
Build complex schemas from reusable parts:
// Base schemas (reusable)
const emailSchema = z.string().email('Please enter a valid email address');
const passwordSchema = z.string().min(6, 'Password must be at least 6 characters');
const abnSchema = z.string().regex(/^\d{11}$/, 'ABN must be 11 digits');
// Composed schemas
const registerFormSchema = z
.object({
email: emailSchema,
password: passwordSchema,
confirmPassword: z.string(),
})
.refine((data) => data.password === data.confirmPassword, {
message: 'Passwords do not match',
path: ['confirmPassword'],
});
Validate data before sending to the backend:
const documentCreateSchema = z.object({
title: z.string().min(1, 'Title is required').max(255),
content: z.string().min(1, 'Content is required'),
metadata: z.record(z.unknown()).optional(),
});
async function createDocument(input: unknown): Promise<Document> {
// Validate before sending — fail fast on the client
const validated = documentCreateSchema.parse(input);
return apiClient.post('/api/documents', validated);
}
// Australian Business Number (ABN) with checksum
const abnSchema = z.string().refine(
(val) => {
if (!/^\d{11}$/.test(val)) return false;
const weights = [10, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19];
const digits = val.split('').map(Number);
digits[0] -= 1;
const sum = digits.reduce((acc, d, i) => acc + d * weights[i], 0);
return sum % 89 === 0;
},
{ message: 'Invalid ABN' }
);
// Australian phone number
const auPhoneSchema = z.string().regex(
/^(\+61|0)[2-478]\d{8}$/,
'Please enter a valid Australian phone number'
);
// Australian postcode
const postcodeSchema = z.string().regex(/^\d{4}$/, 'Postcode must be 4 digits');
// Date in DD/MM/YYYY format
const auDateSchema = z.string().regex(
/^\d{2}\/\d{2}\/\d{4}$/,
'Date must be in DD/MM/YYYY format'
);
The project defines request models in route files or apps/backend/src/api/schemas/:
from pydantic import BaseModel, Field, field_validator
from typing import Optional
class DocumentCreateRequest(BaseModel):
"""Validated input for document creation."""
title: str = Field(
...,
min_length=1,
max_length=255,
description="Document title"
)
content: str = Field(
...,
min_length=1,
description="Document content"
)
metadata: Optional[dict] = Field(
None,
description="Additional metadata"
)
@field_validator("title")
@classmethod
def strip_title(cls, v: str) -> str:
return v.strip()
Use Pydantic's built-in constraints instead of custom validators where possible:
from pydantic import BaseModel, Field, EmailStr
from typing import Annotated
# Constrained types (prefer over custom validators)
PositiveInt = Annotated[int, Field(gt=0)]
PageSize = Annotated[int, Field(ge=1, le=100)]
ShortString = Annotated[str, Field(min_length=1, max_length=255)]
class PaginationParams(BaseModel):
"""Reusable pagination parameters."""
page: PositiveInt = 1
page_size: PageSize = 20
class SearchRequest(BaseModel):
"""Validated search input."""
query: ShortString
pagination: PaginationParams = PaginationParams()
from fastapi import APIRouter
router = APIRouter(prefix="/api/documents", tags=["documents"])
@router.post("/", status_code=201)
async def create_document(
request: DocumentCreateRequest, # Pydantic validates automatically
user: User = Depends(get_current_user),
) -> DocumentResponse:
"""Create a new document. Input is validated by Pydantic."""
# request is already validated — trust it here
return await document_service.create(request, user)
| Schema Type | Location | When to Use |
|---|---|---|
| Route-specific | Inline in route file | Simple schemas used by one endpoint |
| Shared across routes | apps/backend/src/api/schemas/ | Schemas used by multiple endpoints |
| Domain models | apps/backend/src/models/ | Core business models (not request schemas) |
When adding a new feature, verify:
zodResolverz.infer, not manually defined| Pattern | Problem | Correct Approach |
|---|---|---|
| Validation logic inside route handlers | Mixes concerns, hard to reuse or test | Define Zod/Pydantic schemas separately and reference them |
| No error messages on validation failure | Users see raw Pydantic/Zod errors | Provide human-readable messages in en-AU on every field |
Type coercion without validation (Number(input)) | NaN and unexpected values slip through | Use Zod .coerce.number() or Pydantic field_validator |
| Optional fields without defaults | Downstream code must null-check everywhere | Set sensible defaults via Field(default=...) or .default() |
| Manually defining types instead of inferring | Types drift out of sync with schemas | Use z.infer<typeof schema> and Pydantic model exports |
[AGENT_ACTIVATED]: Data Validation
[PHASE]: {Schema Design | Implementation | Review}
[STATUS]: {in_progress | complete}
{validation analysis or schema output}
[NEXT_ACTION]: {what to do next}
Validation failures should use DATA_VALIDATION_* error codes from the error-taxonomy skill:
# Backend: structured validation error
raise HTTPException(
status_code=422,
detail={
"detail": "Email format is invalid",
"error_code": "DATA_VALIDATION_INVALID_FORMAT",
"field": "email",
},
)
min_length, gt) over custom validatorsWhen api-contract skill is installed, Zod and Pydantic schemas should mirror each other to form a typed contract between frontend and backend.
+61 or 0X XXXX XXXX