[autoresearch] baseline: system prompt quality tests (5/5 passing)

This commit is contained in:
evandrodevbr committed 2026-04-11 05:27:36 -03:00
1 parent 05cc036f6d
commit 60b0101717
142 files changed
+18589 -9947

No files matched your search

+62
View File
@@ -0,0 +1,62 @@
---
trigger: model_decision
description: When building UI components, implementing user-facing interfaces, or working with frontend accessibility
---
## Accessibility Principles
### Core Standard
Follow **WCAG 2.1 Level AA** as the baseline for all user-facing interfaces.
### Semantic HTML
- Use semantic elements (`<nav>`, `<main>`, `<article>`, `<section>`, `<button>`, `<form>`) over generic `<div>` and `<span>`
- Each page has exactly one `<h1>`, with proper heading hierarchy (h1 → h2 → h3, no skipping)
- Use `<label>` elements associated with form inputs (via `for`/`id` or wrapping)
- Use `<table>` for tabular data, never for layout
### Keyboard Navigation
- All interactive elements must be reachable via Tab key
- Focus order follows logical reading order
- Focus indicators must be visible (never `outline: none` without replacement)
- Custom components implement proper keyboard patterns (Arrow keys for menus, Escape to close modals)
- No keyboard traps — user can always Tab away from any component
### ARIA Attributes
- **First rule of ARIA:** Don't use ARIA if a native HTML element can do the job
- Use `aria-label` or `aria-labelledby` for elements without visible text labels
- Use `aria-live` regions for dynamic content updates (toasts, notifications)
- Use `role` attributes only for custom widgets that lack native semantics
- Use `aria-expanded`, `aria-controls` for disclosure patterns (dropdowns, accordions)
### Color and Contrast
- Text contrast ratio: minimum **4.5:1** against background (AA normal text)
- Large text contrast ratio: minimum **3:1** (AA large text)
- **Never use color alone** to convey information (add icons, text, or patterns)
- Support both light and dark themes with proper contrast in each
### Images and Media
- All `<img>` elements have descriptive `alt` text (or `alt=""` for decorative images)
- Videos have captions or transcripts
- Audio controls are accessible and labeled
### Forms
- Error messages are programmatically associated with inputs (`aria-describedby`)
- Required fields are indicated both visually and programmatically (`required` attribute)
- Form validation errors are announced to screen readers
### Accessibility Checklist
- [ ] All interactive elements reachable via keyboard?
- [ ] Focus indicators visible on all interactive elements?
- [ ] Semantic HTML elements used (no `<div>` buttons)?
- [ ] Heading hierarchy is correct (h1 → h2 → h3)?
- [ ] Color contrast meets AA standards (4.5:1)?
- [ ] Color is not the sole indicator of meaning?
- [ ] Images have appropriate alt text?
- [ ] Form inputs have associated labels?
- [ ] ARIA used correctly (native first)?
### Related Principles
- Core Design Principles @core-design-principles.md
- Security Principles @security-principles.md (XSS prevention in user content)
- Testing Strategy @testing-strategy.md (accessibility testing)
+127
View File
@@ -0,0 +1,127 @@
---
trigger: model_decision
description: When implementing REST/HTTP APIs (endpoints, handlers, middleware, or response formatting)
---
## API Design Principles
### RESTful API Standards
**Resource-Based URLs:**
- Use plural nouns for resources: `/api/{version}/users`, `/api/{version}/orders`
- Hierarchical relationships: `/api/{version}/users/:userId/orders`
- Avoid verbs in URLs: `/api/{version}/getUser` ❌ → `/api/{version}/users/:id` ✅
**HTTP Methods:**
- GET: Read/retrieve resource (safe, idempotent, cacheable)
- POST: Create new resource (not idempotent)
- PUT: Replace entire resource (idempotent)
- PATCH: Partial update (idempotent)
- DELETE: Remove resource (idempotent)
**Versioning:**
- URL path versioning: `/api/{version}/users` e.g.`/api/v1/users` (explicit, clear)
**Pagination:**
- Limit results per page (default 20, max 100)
- Cursor-based: `?cursor=abc123` (better for real-time data)
- Offset-based: `?page=2&limit=20` (simpler, less accurate for changing data)
**Filtering and Sorting:**
- Filtering: `?status=active&role=admin`
- Sorting: `?sort=created_at:desc,name:asc`
- Searching: `?q=search+term`
### HTTP Status Codes and Error Categories
**Success Codes:**
- 200 OK: Success (GET, PUT, PATCH)
- 201 Created: Resource created successfully (POST)
- 204 No Content: Success with no response body (DELETE)
**Client Error Codes (4xx) - User Can Fix:**
**Validation Errors (400 Bad Request):**
- User input doesn't meet requirements
- Examples: Invalid email, password too short, required field missing
- Response: Detailed field-level errors
- User action: Correct input and retry
**Authentication Errors (401 Unauthorized):**
- Identity verification failed
- Examples: Invalid credentials, expired token, missing token
- User action: Provide valid credentials
**Authorization Errors (403 Forbidden):**
- Permission denied (user identified but lacks permission)
- User action: Contact admin for permission
**Not Found Errors (404 Not Found):**
- Resource doesn't exist or user lacks permission to know it exists
- User action: None (doesn't exist)
**Business Rule Violations (409 Conflict / 422 Unprocessable Entity):**
- Domain rule violations
- Examples: Insufficient balance, duplicate email, order already shipped
- Response: Business rule explanation
- User action: Depends on business context
**Rate Limiting (429 Too Many Requests):**
- Too many requests in time window
- User action: Wait and retry
**Server Error Codes (5xx) - System Issue:**
**Infrastructure Errors (500/502/503):**
- Database down, network timeout, external service failure
- Response: Generic message with correlation ID
- User action: None (system issue, retry later)
### API Success Response Format
```
{
"data": { /* resource or array of resources */ },
"meta": {
"total": 100,
"page": 1,
"perPage": 20
},
"links": {
"self": "/api/v1/users?page=1",
"next": "/api/v1/users?page=2",
"prev": null
}
}
```
### API Error Response Format
All API errors must follow a consistent envelope structure, matching the success format where possible or using a standard error envelope.
```
{
"status": "error", // Transport: Always "error" or "fail"
"code": 400, // Transport: Redundant HTTP Status
"error": { // Domain: The actual business problem
"code": "VALIDATION_ERROR", // Machine-readable business code (UPPER_SNAKE)
"message": "Invalid email format", // Human-readable message
"details": { // Optional: Structured context
"field": "email",
"reason": "Must be a valid address"
},
"correlationId": "req-1234567890", // Ops: Traceability
"doc_url": "https://..." // Optional: Help link
}
}
```
### Related Principles
- Error Handling Principles @error-handling-principles.md
- Security Mandate @security-mandate.md
- Security Principles @security-principles.md
- Logging and Observability Mandate @logging-and-observability-mandate.md
- Data Serialization and Interchange Principles @data-serialization-and-interchange-principles.md
+194
View File
@@ -0,0 +1,194 @@
---
trigger: always_on
---
## Architectural Patterns — Testability-First Design
### Core Principle
All code must be independently testable without running the full application or external infrastructure.
### Universal Architecture Rules
#### Rule 1: I/O Isolation
**Problem:** Tightly coupled I/O makes tests slow, flaky, and environment-dependent.
**Solution:** Abstract all I/O behind interfaces/contracts:
- Database queries
- HTTP calls (to external APIs)
- File system operations
- Time/randomness (for determinism)
- Message queues
**Implementation Discovery:**
1. Search the codebase for existing abstraction patterns by looking for symbols named: `Interface`, `Mock`, `Repository`, `Store`, `Adapter` (use your available codebase search tool — Pathfinder, grep, IDE symbol search, etc.)
2. Match the style (interface in Go, Protocol in Python, interface in TypeScript)
3. Implement production adapter AND test adapter
**Example (Go):**
```Go
// Contract
type UserStore interface {
Create(ctx context.Context, user User) error
GetByEmail(ctx context.Context, email string) (*User, error)
}
// Production adapter
type PostgresUserStore struct { /* ... */ }
// Test adapter
type MockUserStore struct { /* ... */ }
```
**Example (TypeScript/Vue):**
```typescript
// Contract (service layer)
export interface TaskAPI {
createTask(title: string): Promise<Task>;
getTasks(): Promise<Task[]>;
}
// Production adapter
export class BackendTaskAPI implements TaskAPI { /* ... */ }
// Test adapter (vi.mock or manual)
export class MockTaskAPI implements TaskAPI { /* ... */ }
```
#### Rule 2: Pure Business Logic
**Problem:** Business rules mixed with I/O are impossible to test without infrastructure.
**Solution:** Extract calculations, validations, transformations into pure functions:
- Input → Output, no side effects
- Deterministic: same input = same output
- No I/O inside business rules
**Examples:**
```
// ✅ Pure function - easy to test
func calculateDiscount(items []Item, coupon Coupon) (float64, error) {
// Pure calculation, returns value
}
// ❌ Impure - database call inside
func calculateDiscount(ctx context.Context, items []Item, coupon Coupon) (float64, error) {
validCoupon, err := db.GetCoupon(ctx, coupon.ID) // NO!
}
```
**Correct approach:**
```
// 1. Fetch dependencies first (in handler/service)
validCoupon, err := store.GetCoupon(ctx, coupon.ID)
// 2. Pass to pure logic
discount, err := calculateDiscount(items, validCoupon)
// 3. Persist result
err = store.SaveOrder(ctx, order)
```
#### Rule 3: Dependency Direction
**Principle:** Dependencies point inward toward business logic.
```
┌──────────────────────────────────────┐
│ Infrastructure Layer │
│ (DB, HTTP, Files, External APIs) │
│ │
│ Depends on ↓ │
└──────────────────────────────────────┘
↓
┌──────────────────────────────────────┐
│ Contracts/Interfaces Layer │
│ (Abstract ports - no implementation)│
│ │
│ Depends on ↓ │
└──────────────────────────────────────┘
↓
┌──────────────────────────────────────┐
│ Business Logic Layer │
│ (Pure functions, domain rules) │
│ NO dependencies on infrastructure │
└──────────────────────────────────────┘
```
**Never:**
- Business logic imports database driver
- Domain entities import HTTP framework
- Core calculations import config files
**Always:**
- Infrastructure implements interfaces defined by business layer
- Business logic receives dependencies via injection
### Pattern Discovery Protocol
**Before implementing ANY feature:**
1. **Search existing patterns** (MANDATORY):
Search the codebase for symbols named: `Interface`, `Repository`, `Service`, `Store`, `Mock`
(use your available codebase search tool — Pathfinder, grep, IDE symbol search, etc.)
2. **Examine 3 existing modules** for consistency:
- How do they handle database access?
- Where are pure functions vs I/O operations?
- What testing patterns exist?
3. **Document pattern** (>80% consistency required):
- "Following pattern from [task, user, auth] modules"
- "X/Y modules use interface-based stores"
- "All tests use [MockStore, vi.mock, TestingPinia] pattern"
4. **If consistency <80%**: STOP and report fragmentation to human.
### Testability Compliance
These are **architectural** requirements that the code structure must satisfy — not testing mechanics. A design that cannot meet them is not compliant with this architecture.
**Unit testability (non-negotiable):**
- Unit tests MUST run without starting any database, external service, or network call
- All I/O dependencies MUST be abstractable to the point where a mock can replace them
- Business logic MUST be exercisable in isolation from infrastructure (enforced by Rules 1 & 2)
**Integration testability:**
- Every I/O adapter MUST be independently testable against real infrastructure
- Adapters must be replaceable — the application must not hard-wire a specific implementation
**Test co-location (structural rule):**
- Default: Unit and integration tests co-locate with the implementation they test (test lives next to the code)
- E2E tests: isolated in a dedicated directory — `e2e/` at the project root for single-app projects, or `apps/e2e/` for monorepos. They cross feature boundaries and belong to none. See Testing Strategy @testing-strategy.md for the complete E2E directory layout.
- **Language overrides:** Some ecosystems have different conventions (e.g., Flutter uses a mirrored `test/` directory, Rust uses inline `#[cfg(test)]` blocks). When a language-specific project structure file exists, its test location rules take precedence over this default. See the relevant `project-structure-*` file for authoritative guidance.
> For test pyramid proportions, naming conventions, tool choices, and language-specific tooling, see Testing Strategy @testing-strategy.md.
### Language-Specific Idioms
For language-specific abstraction patterns and test tooling, see the dedicated idiom files listed in `code-idioms-and-conventions.md`. Those files contain the authoritative guidance per ecosystem and are independently maintainable.
**Universal rule:** In every language, the testability pattern is the same — I/O is behind an interface, business logic is pure, and tests inject a mock/fake implementation. The idiom files describe *how* to express this pattern in each ecosystem's syntax.
### Enforcement Checklist
Before marking code complete, verify:
- [ ] Can I run unit tests without starting database/external services?
- [ ] Are all I/O operations behind an abstraction?
- [ ] Is business logic pure (no side effects)?
- [ ] Do integration tests exist for all adapters?
- [ ] Does pattern match existing codebase (>80% consistency)?
### Related Principles
- Core Design Principles @core-design-principles.md
- Testing Strategy @testing-strategy.md
- Code Organization Principles @code-organization-principles.md
- Project Structure @project-structure.md
- Database Design Principles @database-design-principles.md
+171
View File
@@ -0,0 +1,171 @@
---
trigger: model_decision
description: >
When configuring Kubernetes deployments, GitOps infrastructure, or deployment
strategies (blue-green, canary, rolling). Load this file in addition to
ci-cd-principles.md when the tech stack uses Kubernetes or a managed container
platform with orchestration (GKE, EKS, AKS).
---
## CI/CD — GitOps and Kubernetes Deployment
> **Agent scope:** This is a supplement to `ci-cd-principles.md` (Level 2).
> Apply only when the project targets Kubernetes or a Kubernetes-based managed platform.
> Do not apply rolling/canary/blue-green patterns to Docker Compose or serverless deployments.
---
### Choosing a Deployment Strategy
| Strategy | When to use | Trade-off |
|----------|-------------|-----------|
| **Rolling** | Default for most services; SLO requirements | Simple, but mixes versions briefly |
| **Blue-Green** | Zero-downtime with instant rollback | Doubles infrastructure cost during switch |
| **Canary** | Risk-reducing incremental rollout; A/B variants | Requires traffic splitting capability |
---
### Rolling Deployment (Default)
Kubernetes native. Suitable for most stateless services.
```yaml
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25% # Max pods above desired count during update
maxUnavailable: 0% # Zero-downtime: don't remove pods before new ones ready
```
**Rules:**
- Set `maxUnavailable: 0` for production services with SLO requirements
- Set `minReadySeconds` to let pods stabilize before proceeding
- Configure `terminationGracePeriodSeconds` to finish in-flight requests before shutdown
---
### Blue-Green Deployment
**When:** Zero-downtime deployments where rollback must be instant and clean.
```
┌──────────────┐ ┌──────────────┐
│ Blue (v1) │ │ Green (v2) │
│ [LIVE 100%] │────→│ [STANDBY] │
└──────────────┘ └──────────────┘
↕ Switch load balancer
┌──────────────┐ ┌──────────────┐
│ Blue (v1) │ │ Green (v2) │
│ [STANDBY] │ │ [LIVE 100%] │
└──────────────┘ └──────────────┘
```
**Rules:**
- Both environments must be identical in infrastructure
- Run smoke tests against green before switching traffic
- Keep blue alive for at least 30 minutes post-switch (fast rollback window)
- Database migrations must be backward-compatible (blue still runs against the same DB)
---
### Canary Deployment
**When:** Risk-reducing incremental rollout; A/B testing deployment variants.
```
Traffic split during rollout:
5% → canary (v2)
95% → stable (v1)
↓ metrics look good
25% → canary
75% → stable
↓ bake time passes
100% → canary (now stable)
```
**Rules:**
- Define success metrics before starting rollout (error rate, latency SLO)
- Set automatic rollback threshold: if canary error rate > 2× baseline → auto-rollback
- Minimum bake time per traffic increment: 15–30 minutes
- Feature flags complement canary routing for functional (not just traffic) testing
---
### GitOps Pattern
For Kubernetes environments, use **declarative GitOps** instead of imperative `kubectl apply`.
**Pattern:**
```
Application Repo (code) → CI builds image → updates tag in Config Repo
Config Repo (K8s manifests) → ArgoCD/Flux syncs to cluster automatically
```
**Rules:**
- Git is the **single source of truth** for cluster state
- All changes to production go through PRs on the config repo — no direct `kubectl` in prod
- ArgoCD/Flux continuously reconciles — any manual drift is auto-corrected
- Secrets reference external secret stores — **never plaintext in git**
#### Example ArgoCD Application
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-production
spec:
source:
repoURL: https://github.com/org/config-repo
path: environments/production/myapp
targetRevision: HEAD
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true # Remove resources deleted from git
selfHeal: true # Reconcile manual drift
```
---
### Secrets Management in Kubernetes
Never store plaintext secrets in git — even in private repositories.
| Tool | Pattern |
|------|---------|
| **External Secrets Operator** | Syncs secrets from AWS Secrets Manager, GCP Secret Manager, Vault into K8s Secrets |
| **Sealed Secrets** | Encrypts secrets with a cluster-held key; safe to commit encrypted form |
| **Vault Agent Injector** | Sidecars inject secrets directly into pods at runtime |
Choose based on your cloud provider or existing tooling. Document the choice in your
technical architecture document.
---
### Kubernetes CI/CD Checklist
- [ ] Deployment strategy defined and documented (rolling / blue-green / canary)?
- [ ] `maxUnavailable: 0` set for production services with SLO?
- [ ] `terminationGracePeriodSeconds` configured for graceful shutdown?
- [ ] GitOps in place — no direct `kubectl apply` commands in production CI pipeline?
- [ ] Config repo exists, separate from application repo?
- [ ] All PRs to config repo require review before ArgoCD/Flux sync?
- [ ] Secrets stored in external secret store, not plaintext in git?
- [ ] Rollback procedure tested and documented?
---
### Related Principles
- CI/CD Principles @ci-cd-principles.md (core pipeline — read first)
- Security Principles @security-principles.md (secrets management)
- Monitoring and Alerting Principles @monitoring-and-alerting-principles.md (canary success metrics)
- Feature Flags @feature-flags-principles.md (canary + feature flag interaction)
+348
View File
@@ -0,0 +1,348 @@
---
trigger: model_decision
description: When configuring CI/CD pipelines, deployment processes, release strategies, or build configurations
---
## CI/CD Principles
> **Agent scope:** This rule applies when writing CI/CD manifests
> (Dockerfile, docker-compose, GitHub Actions, GitLab CI, etc.).
> It is layered by deployment complexity — apply only the levels relevant to the project.
---
### Deployment Complexity Levels
| Level | Applies When | Key Additions |
|-------|-------------|---------------|
| **0 — All projects** | Always | Lint, test, security scan, secrets management |
| **1 — Containerized** | Docker image is the artifact | Multi-stage build, image scan, SBOM attestation |
| **2 — Orchestrated** | Kubernetes or managed container platform | Deployment strategies, GitOps |
Load supplementary rules when reaching Level 2:
- Deployment strategies + GitOps → **@ci-cd-gitops-kubernetes.md**
---
### Level 0 — Universal Pipeline Design
**Pipeline Stages (in order):**
1. **Lint** — static analysis, formatting checks
2. **Build** — compile, bundle, generate artifacts
3. **Unit Test** — fast tests with mocked dependencies
4. **Integration Test** — tests against real dependencies (Testcontainers)
5. **Security Scan** — dependency audit, SAST, secrets detection
6. **Deploy** — push to target environment
**Rules:**
- **Fail fast** — run cheapest checks first (lint before build, build before test)
- **Pipeline must be deterministic** — same input = same output, every time
- **Keep pipelines under 15 minutes** — optimize slow stages
- **Never skip failing steps** — fix the pipeline, don't bypass it
- **Build once, deploy many** — same artifact promotes through all environments
---
### Level 0 — Deploy Target Examples
The deploy stage varies by target. The pipeline stages before it are identical.
**Docker Compose (local / staging):**
```bash
docker compose up --build
```
**Cloud Run:**
```bash
gcloud run deploy myapp \
--image gcr.io/project/myapp:$GIT_SHA \
--region us-central1
```
**Vercel (frontend SPA):**
```bash
vercel deploy --prod
```
**Kubernetes:**
Use GitOps — see **@ci-cd-gitops-kubernetes.md**.
---
### Level 0 — Manifest Patterns
#### GitHub Actions
```yaml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod # Pin via go.mod
cache: true # Cache dependencies
- run: gofumpt -l -e -d .
- run: go vet ./...
- run: staticcheck ./...
test:
needs: lint # Fail fast: lint before test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- run: go test -race -cover ./...
security:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Scan for secrets
uses: trufflesecurity/trufflehog@v3 # Pin to release tag
- name: Audit dependencies
run: go run golang.org/x/vuln/cmd/govulncheck@latest ./...
```
**Rules:**
- Pin action versions (`@v4`, not `@latest` or `@main`)
- Use `needs:` to enforce stage ordering
- Cache dependencies (`cache: true` in setup actions)
- Use `go-version-file` / `node-version-file` instead of hardcoding versions
- Never put secrets in workflow files — use `${{ secrets.NAME }}`
---
### Level 1 — Containerized Projects
#### Dockerfile (Multi-Stage Build)
```dockerfile
# Stage 1: Build
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download # Cache dependencies
COPY . .
RUN CGO_ENABLED=0 go build -o /bin/api ./cmd/api
# Stage 2: Runtime (minimal image)
FROM gcr.io/distroless/static-debian12
COPY --from=builder /bin/api /bin/api
EXPOSE 8080
CMD ["/bin/api"]
```
**Rules:**
- Always use multi-stage builds (build → runtime)
- Pin base image versions (never use `:latest`)
- Copy dependency files first, then source (layer caching)
- Use minimal runtime images (distroless, alpine, scratch)
- Never copy `.env`, secrets, or `.git` into images
#### Docker Compose (Local Development)
```yaml
services:
backend:
build:
context: ./apps/backend # Path per project-structure.md
ports:
- "8080:8080"
env_file: .env # Environment config
depends_on:
postgres:
condition: service_healthy
postgres:
image: postgres:16-alpine # Pin versions
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
interval: 5s
timeout: 5s
retries: 5
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
```
**Rules:**
- Always define health checks for dependencies
- Use `depends_on` with `condition: service_healthy`
- Pin all image versions
- Use volumes for persistent data
- Never hardcode credentials — use env_file or environment variables
#### Image Scan + SBOM Attestation
After building and pushing a container image, scan it and attach a signed SBOM attestation.
**Preferred approach: Cosign keyless signing (no key management required)**
Cosign integrates with your CI provider's OIDC token (GitHub Actions, GitLab CI) to sign
images and attestations without storing or rotating cryptographic keys. The signature is
anchored to a transparency log (Rekor), making it auditable and policy-enforceable.
```yaml
build:
needs: security
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write # Required for Cosign keyless signing
steps:
- uses: actions/checkout@v4
- name: Install Cosign
uses: sigstore/cosign-installer@v3
- name: Build and push image
id: build
run: |
docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
docker push ghcr.io/${{ github.repository }}:${{ github.sha }}
- name: Scan container image
run: |
trivy image \
--severity HIGH,CRITICAL \
--exit-code 1 \
ghcr.io/${{ github.repository }}:${{ github.sha }}
- name: Generate SBOM
run: |
syft ghcr.io/${{ github.repository }}:${{ github.sha }} \
-o cyclonedx-json > sbom.json
- name: Attest SBOM to image (keyless, OIDC-backed)
run: |
cosign attest \
--predicate sbom.json \
--type cyclonedx \
ghcr.io/${{ github.repository }}:${{ github.sha }}
# Cosign uses the GitHub Actions OIDC token automatically.
# No COSIGN_PASSWORD or secret keys required.
```
**How the SBOM travels with the image:**
The SBOM attestation is stored as an OCI reference in the same container registry alongside
the image digest. It requires no additional infrastructure — any registry that supports OCI
artifacts (ghcr.io, Google Artifact Registry, AWS ECR, Docker Hub) works out of the box.
To verify the attestation at any time:
```bash
cosign verify-attestation \
--type cyclonedx \
ghcr.io/org/app@sha256:<digest>
```
**Use ORAS instead of Cosign when:**
- You need to attach arbitrary supply chain artifacts (scan reports, provenance JSON, build logs)
that go beyond what Cosign's attestation model covers.
- `oras attach ghcr.io/org/app@sha256:<digest> scan-report.json`
**Rules:**
- Prefer Cosign keyless signing — eliminates secret key management overhead
- SBOM is attached to the image in the OCI registry — not stored as a CI artifact
- Scan BEFORE attesting — the SBOM reflects the scanned image
- For non-containerized apps (Vercel, Netlify frontend), use `npm audit`/`yarn audit` instead;
no SBOM attachment applies
---
### Deployment vs Release (Feature Flags)
Code deployment and feature release are separate concerns. When the PRD or technical
architecture explicitly requires gradual rollout, A/B testing, or kill switches, feature
flags can decouple them.
> **Agent rule:** Do NOT implement feature flags unless explicitly required by the
> PRD or technical architecture document. See **@feature-flags-principles.md** for
> implementation guidance when they are required.
---
### Environment Promotion
```
dev → staging → production
```
- **Dev:** Deployed on every push to feature branch
- **Staging:** Deployed on merge to main/develop
- **Production:** Deployed via manual approval or automated release
**Rules:**
- Same artifacts promote through environments (build once, deploy many)
- Environment-specific config via environment variables, not build flags
- Never deploy directly to production without staging validation
---
### CI/CD Checklist
**Always (all projects):**
- [ ] Pipeline stages run in correct order (lint → build → test → security → deploy)?
- [ ] All versions pinned (base images, CI actions, tool versions)?
- [ ] Dependency caching enabled?
- [ ] No secrets in config files (use env vars or secrets manager)?
- [ ] Secret scanning in CI?
- [ ] Health checks defined for all service dependencies?
- [ ] Pipeline completes in under 15 minutes?
**If building container images (Level 1):**
- [ ] Multi-stage Docker builds used?
- [ ] Container image scanned for HIGH/CRITICAL CVEs?
- [ ] SBOM generated and attested to image via Cosign (keyless)?
**If deploying to Kubernetes (Level 2):**
- [ ] Deployment strategy defined (blue-green, canary, or rolling)?
- [ ] GitOps in place — no direct `kubectl apply` in production?
- [ ] Secrets reference external store, not plaintext in git?
- See **@ci-cd-gitops-kubernetes.md** for full checklist
**If feature flags are required by PRD/architecture:**
- [ ] Flag infrastructure specified in tech architecture document?
- [ ] Every flag has an owner and expiry date?
- See **@feature-flags-principles.md** for full checklist
---
### Related Principles
- Code Completion Mandate @code-completion-mandate.md (validation before ship)
- Security Mandate @security-mandate.md (secrets management)
- Security Principles @security-principles.md (image scanning, SBOM)
- Git Workflow Principles @git-workflow-principles.md (branch strategy)
- Project Structure @project-structure.md (service paths)
- Testing Strategy @testing-strategy.md (unit and integration test stages)
- GitOps + Kubernetes Deployment @ci-cd-gitops-kubernetes.md
- Feature Flags @feature-flags-principles.md
+50
View File
@@ -0,0 +1,50 @@
---
trigger: always_on
---
## Code Completion Mandate
### Universal Requirement
**Before marking any code task as complete, you MUST run automated quality checks and remediate all issues.**
This is NOT OPTIONAL. Delivering code without validation violates the Rugged Software Constitution @rugged-software-constitution.md.
### The Completion Checklist
Every code generation task follows this workflow:
1. **Generate** - Write the code based on requirements
2. **Validate** - Run language-appropriate quality checks (see below)
3. **Remediate** - Fix all detected issues
4. **Verify** - Re-run checks to confirm fixes
5. **Deliver** - Mark task complete only after all checks pass
**Never skip validation "to save time." Validation IS the work.**
### Language-Specific Quality Commands
The authoritative commands for each language live in the corresponding idiom file — this keeps the work of maintaining them close to the language expertise. Load the relevant file to get the exact commands to run:
| Language | Idiom File | Commands Section |
| -------------------- | ---------------------------------- | -------------------------------- |
| **Go** | @go-idioms-and-patterns.md | § Formatting and Static Analysis |
| **TypeScript / Vue** | @typescript-idioms-and-patterns.md | § Formatting and Static Analysis |
| **Vue 3** | @vue-idioms-and-patterns.md | § Linting and Type Checking |
| **Flutter / Dart** | @flutter-idioms-and-patterns.md | § Linting and Formatting |
| **Rust** | @rust-idioms-and-patterns.md | § Clippy and Formatting |
### Failure Protocol
**If any quality check fails:**
1. Read the error output completely
2. Fix the identified issues in the code
3. Re-run the failing command
4. Do not proceed until all checks pass
> Never disable a lint rule or suppress a warning to make checks pass. Fix the root cause.
### Related Principles
- Rugged Software Constitution @rugged-software-constitution.md
- Code Idioms and Conventions @code-idioms-and-conventions.md
@@ -0,0 +1,46 @@
---
trigger: always_on
---
## Code Idioms and Conventions
### Universal Principle
**Write idiomatic code for the target language:**
- Code should look natural to developers familiar with that language
- Follow established community conventions, not personal preferences
- Use language built-ins and standard library effectively
- Apply language-appropriate patterns (don't force patterns from other languages)
### Idiomatic Code Characteristics
- Leverages language features (don't avoid features unnecessarily)
- Follows language naming conventions
- Uses appropriate error handling for language (exceptions vs Result types)
- Applies established community patterns
### Avoid Cross-Language Anti-Patterns
- ❌ Don't write "Java in Python" or "C in Go"
- ❌ Don't force OOP patterns in functional languages
- ❌ Don't avoid language features because they're "unfamiliar"
- ✅ Learn and apply language-specific idioms
### Language-Specific Idioms
This file defines the universal principle. Each language has a dedicated file with concrete patterns, tooling choices, and idiom-specific rules. **Load the relevant file when working in that language** — it is the authoritative source for that ecosystem.
| Language / Framework | Idiom File | When to Load |
| -------------------- | ---------------------------------- | ----------------------------------------------- |
| **Go** | @go-idioms-and-patterns.md | Go services, APIs, CLI tools |
| **TypeScript** | @typescript-idioms-and-patterns.md | Any TypeScript project (backend, frontend, CLI) |
| **Vue 3** | @vue-idioms-and-patterns.md | Vue components, Pinia stores, composables |
| **Flutter / Dart** | @flutter-idioms-and-patterns.md | Mobile apps, Flutter widgets, Riverpod |
| **Rust** | @rust-idioms-and-patterns.md | Rust binaries, libraries, workspaces |
> Each language-specific file is independently changeable. It governs only its own ecosystem's coding idioms and defers to the relevant cross-cutting principle files (error handling, testing, logging, etc.) for universal guidance.
### Related Principles
- Core Design Principles @core-design-principles.md
- Code Completion Mandate @code-completion-mandate.md
@@ -0,0 +1,115 @@
---
trigger: always_on
---
## Code Organization Principles
- Generate small, focused functions with clear single purposes (typically 10-50 lines)
- Keep cognitive complexity low (cyclomatic complexity < 10 for most functions)
- Maintain clear boundaries between different layers (presentation, business logic, data access)
- Design for testability from the start, avoiding tight coupling that prevents testing
- Apply consistent naming conventions that reveal intent without requiring comments
### Module Boundaries
**Problem:** Cross-module coupling makes changes ripple across codebase.
**Solution:** Feature-based organization with clear public interfaces:
- One feature = one directory
- Each module exposes a public API (exported functions/classes)
- Internal implementation details are private
- Cross-module calls only through public API
**Directory Structure (Language-Agnostic):**
> Paths below are illustrative examples following `project-structure.md` — the single source of truth for project layout.
> **Note:** The generic filenames below are illustrative. Always use language-specific naming conventions from the relevant `project-structure-*` file (e.g., `storage.go` in Go, `task.api.ts` in TypeScript, `task_repository.dart` in Dart).
```
/task
- public_api.{ext} # Exported interface
- business.{ext} # Pure logic
- store.{ext} # I/O abstraction (interface)
- postgres.{ext} # I/O implementation
- mock.{ext} # Test implementation
- test.{ext} # Unit tests (mocked I/O)
- integration.test.{ext} # Integration tests (real I/O)
```
**Go Example:**
```
apps/backend/internal/features/task/
- service.go # Public API of this feature (Service struct)
- handler.go # HTTP handlers
- logic.go # Pure domain logic
- storage.go # interface Storage
- storage_pg.go # implements Storage (PostgreSQL)
- storage_mock.go # Mock implementation for testing
- logic_test.go # Unit tests with MockStorage
- postgres_integration_test.go # Integration test with Testcontainers
```
**Vue Example:**
```
/apps/frontend/src/features/task
- index.ts # Public exports
- task.service.ts # Business logic
- task.api.ts # interface TaskAPI
- task.api.backend.ts # implements TaskAPI
- task.store.ts # Pinia store (uses TaskAPI)
- task.service.spec.ts # Unit tests (mock API)
```
### Feature Interaction Patterns
**Direct Import**
When a feature needs another feature's capabilities, import its Service directly:
```go
// In features/order/logic.go
import "yourapp/internal/features/task"
type Logic struct {
taskService *task.Service // Direct dependency injection
}
func (l *Logic) CreateOrder(ctx context.Context, req CreateOrderRequest) error {
// Use task service directly
task, err := l.taskService.GetTask(ctx, req.TaskID)
// ... rest of logic
}
```
**Rules**
- Only import Service (public API), never internal files like logic.go or storage.go
- Declare dependency in the dependent feature's Service constructor
- Wire dependencies in cmd/api/main.go
**Wiring Example**
```go
// cmd/api/main.go
taskService := task.NewService(taskStorage)
orderService := order.NewService(orderStorage, taskService) // Pass task service
```
### Avoid Circular Dependencies
**Problem:** Module A imports B, B imports A
- Causes build failures, initialization issues
- Indicates poor module boundaries
**Solution:**
- Extract shared code to third module
- Restructure dependencies (A→C, B→C)
- Use dependency injection
### Related Principles
- Core Design Principles @core-design-principles.md
- Project Structure @project-structure.md
- Architectural Patterns — Testability-First Design @architectural-pattern.md
@@ -0,0 +1,54 @@
---
trigger: model_decision
description: When executing external commands, shell scripts, or system processes
---
## Command Execution Principles
### Security
**Never execute user input directly:**
- ❌ `exec(userInput)`
- ❌ `shell("rm " + userFile)`
- ✅ Use argument lists, not shell string concatenation
- ✅ Validate and sanitize all arguments
**Run with minimum permissions:**
- Never run commands as root/admin without explicit human approval. If elevated permissions are absolutely required, STOP and request authorization.
- Use least-privilege service accounts
### Portability
**Use language standard library:**
- Avoid shell commands when standard library provides functionality
- Example: Use file I/O APIs instead of `cat`, `cp`, `mv`
**Test on all target OS:**
- Windows, Linux, macOS have different commands and behaviors
- Use path joining functions (don't concatenate with /)
### Error Handling
**Check exit codes:**
- Non-zero exit code = failure
- Capture and log stderr
- Set timeouts for long-running commands
- Handle "command not found" gracefully
### Command Execution Checklist
- [ ] Is user input sanitized/validated before use in commands?
- [ ] Are arguments passed as lists (not shell string concatenation)?
- [ ] Are commands running with minimum necessary permissions?
- [ ] Are exit codes checked and errors handled?
- [ ] Are timeouts set for long-running commands?
- [ ] Is stderr captured and logged?
### Related Principles
- Security Mandate @security-mandate.md
- Security Principles @security-principles.md
@@ -0,0 +1,25 @@
---
trigger: always_on
---
## Concurrency and Threading Mandate
### When to Use Concurrency
**I/O-Bound Operations:** Use asynchronous I/O, event-driven concurrency, or coroutines when waiting on network requests, file I/O, or database queries.
**CPU-Bound Operations:** Use OS threads or thread pools for heavy computation, data processing, or encoding work where CPU cycles dominate.
**Don't Over-Use Concurrency:** Concurrency adds significant complexity (race conditions, deadlocks, debugging difficulty). Profile first — only add concurrency when there is a measurable performance benefit.
For implementation details (race condition prevention, deadlock avoidance, message passing patterns), see Concurrency and Threading Principles @concurrency-and-threading-principles.md.
### When NOT to Use Concurrency
- Simple synchronous operations
- No measurable performance benefit
- Avoid premature optimization
### Related Principles
- Concurrency and Threading Principles @concurrency-and-threading-principles.md
- Performance Optimization Principles @performance-optimization-principles.md
- Rust Idioms and Patterns @rust-idioms-and-patterns.md
@@ -0,0 +1,89 @@
---
trigger: model_decision
description: When implementing concurrent, parallel, or multi-threaded code (async/await, threads, goroutines, actors)
---
## Concurrency and Threading Principles
**1. Avoid Race Conditions**
**What is a race condition:**
- Multiple threads access shared data concurrently
- At least one thread writes/modifies the data
- No synchronization mechanism in place
- Result depends on unpredictable thread execution timing
**Prevention strategies:**
- Synchronization: Locks, mutexes, semaphores
- Immutability: Immutable data is thread-safe by default
- Message passing: Send data between threads instead of sharing
- Thread-local storage: Each thread has its own copy
**Detection:**
- Go: Run with `-race` flag (race detector)
- Rust: Miri tool for undefined behavior detection
- C/C++: ThreadSanitizer (TSan)
- Java: JCStress, FindBugs
**2. Prevent Deadlocks**
**What is a deadlock:**
- Two or more threads waiting for each other indefinitely
- Example: Thread A holds Lock 1, waits for Lock 2; Thread B holds Lock 2, waits for Lock 1
**Four conditions (ALL must be true for deadlock):**
1. Mutual exclusion: Resources held exclusively (locks)
2. Hold and wait: Holding one resource while waiting for another
3. No preemption: Can't force unlock
4. Circular wait: A waits for B, B waits for A
**Prevention (break any one condition):**
- Lock ordering: Always acquire locks in same order
- Timeout: Use try_lock with timeout, back off and retry
- Avoid nested locks: Don't hold multiple locks simultaneously
- Use lock-free data structures when possible
**3. Prefer Immutability**
- Immutable data = thread-safe by default (no synchronization needed)
- Share immutable data freely between threads
- Use immutable data structures where possible (Rust default, functional languages)
- If data must change, use message passing instead of shared mutable state
**4. Message Passing Over Shared Memory**
- "Don't communicate by sharing memory; share memory by communicating" (Go proverb)
- Send data through channels/queues instead of accessing shared memory
- Reduces need for locks and synchronization
- Easier to reason about and test
**5. Graceful Degradation**
- Handle concurrency errors gracefully (timeouts, retries, circuit breakers)
- Don't crash entire application on one thread failure
- Use supervisors/monitors for fault tolerance (Erlang/Elixir actor model)
- Implement backpressure for producer-consumer scenarios
### Concurrency Models by Use Case
- **I/O-bound:** async/await, event loops, coroutines, green threads
- **CPU-bound:** OS threads, thread pools, parallel processing
- **Actor model:** Erlang/Elixir actors, Akka (message passing, isolated state)
- **CSP (Communicating Sequential Processes):** Go channels, Rust channels
### Testing Concurrent Code
- Write unit tests with controlled concurrency (deterministic execution)
- Test timeout scenarios and resource exhaustion
- Test thread pool full, queue full scenarios
### Related Principles
- Resource and Memory Management Principles @resources-and-memory-management-principles.md
- Error Handling Principles @error-handling-principles.md
- Testing Strategy @testing-strategy.md
@@ -0,0 +1,110 @@
---
trigger: model_decision
description: When working with application configuration, environment variables, settings files, or secrets management
---
## Configuration Management Principles
### Separation of Configuration and Code
**Configuration:**
- Environment-specific values (URLs, credentials, timeouts)
- Changes between dev/staging/prod
- Can change without code deployment
**Code:**
- Business logic and application behavior
- Same across all environments
- Requires deployment to change
**Never hardcode configuration in code:**
- ❌ `const DB_URL = "postgresql://prod-db:5432/myapp"`
- ✅ `const DB_URL = process.env.DATABASE_URL`
### Configuration Validation
**Validate at startup:**
- Check all required configuration is present
- Fail fast if required config is missing or invalid
- Provide clear error messages for misconfiguration
- Example: "DATABASE_URL environment variable is required"
**Validation checks:**
- Type (string, number, boolean, enum)
- Format (URL, email, file path)
- Range (port numbers 1-65535)
- Dependencies (if feature X enabled, config Y required)
### Configuration Hierarchy
**Precedence (highest to lowest):**
1. **Command-line arguments:** Override everything (for testing, debugging)
2. **Environment variables:** Override config files
3. **Config files:** Environment-specific (config.prod.yaml, config.dev.yaml)
4. **Defaults:** Reasonable defaults in code (fallback)
**Example:**
Database port resolution:
1. Check CLI arg: --db-port=5433
2. Check env var: DB_PORT=5432
3. Check config file: database.port=5432
4. Use default: 5432
### Configuration Organization
Hybrid Approach (config files + .env files): define the structure of configuration in config files (e.g. config/database.yaml) and use .env files to inject the secret values.
**.env files:** Description: A file dedicated to a specific environment (development) for production these values comes from secrets/environment platfrom or manager not a physical `.env` file on disk. When to Use: Use this only for secrets (API keys, passwords) and a few environment-specific values (like a server IP). These files except `.env.template` should never be committed to version control (git).
- `.env.template` - Consist of credentials and secrets with blank value (SHOULD commit to git)
- `.env.development` - Local development credentials and secrets (SHOULD NOT commit to git)
**Example `.env.development`:**
```
DEV_DB_HOST=123.45.67.89
DEV_DB_USERNAME=prod_user
DEV_DB_PASSWORD=a_very_secure_production_password
```
**Feature files:** Description: Settings are grouped into files based on what they do (database, auth, etc.). This keeps your configuration organized. When to Use: Use this as your primary method for organizing non-secret settings. It’s the best way to keep your configuration clean and scalable as your application grows.
- `config/database.yaml` - Database settings
- `config/redis.yaml` - Cache settings
- `config/auth.yaml` - Authentication settings
**Example `config/database.yaml`:**
```
default: &default
adapter: postgresql
pool: 5
development:
<<: *default
host: localhost
database: myapp_dev
username: <%= ENV['DEV_DB_USERNAME'] %> # Placeholder for a secret
password: <%= ENV['DEV_DB_PASSWORD'] %>
production:
<<: *default
host: <%= ENV['PROD_DB_HOST'] %>
database: myapp_prod
username: <%= ENV['PROD_DB_USERNAME'] %>
password: <%= ENV['PROD_DB_PASSWORD'] %>
```
> **Note:** Feature flag configuration is a distinct, PRD-gated concern — not routine application configuration. See Feature Flags Principles @feature-flags-principles.md for when and how feature flags are used.
### Related Principles
- Security Mandate @security-mandate.md
- Security Principles @security-principles.md
- Feature Flags Principles @feature-flags-principles.md
+92
View File
@@ -0,0 +1,92 @@
---
trigger: always_on
---
## Core Design Principles
### SOLID Principles
**Single Responsibility Principle (SRP):**
- Each class, module, or function should have ONE and ONLY ONE reason to change
- Generate focused, cohesive units of functionality
- If explaining what something does requires "and", it likely violates SRP
**Open/Closed Principle (OCP):**
- Software entities should be open for extension but closed for modification
- Design abstractions (interfaces, ports) that allow behavior changes without modifying existing code
- Use composition and dependency injection to enable extensibility
**Liskov Substitution Principle (LSP):**
- Subtypes must be substitutable for their base types without altering program correctness
- Inheritance hierarchies must maintain behavioral consistency
- If substituting a subclass breaks functionality, LSP is violated
**Interface Segregation Principle (ISP):**
- Clients should not be forced to depend on interfaces they don't use
- Create focused, role-specific interfaces rather than monolithic ones
- Many small, cohesive interfaces > one large, general-purpose interface
**Dependency Inversion Principle (DIP):**
- Depend on abstractions (interfaces/ports), not concretions (implementations/adapters)
- High-level modules should not depend on low-level modules; both should depend on abstractions
- Core principle enabling Testability-First architecture
### Essential Design Practices
**DRY (Don't Repeat Yourself):**
- Eliminate code duplication through proper abstraction, shared utilities, composable functions
- Each piece of knowledge should have single, authoritative representation
- Don't duplicate logic, algorithms, or business rules
**YAGNI (You Aren't Gonna Need It):**
**CRITICAL:** Code maintainability always prevail
- Avoid implementing functionality before it's actually required
- Don't add features based on speculation about future needs
- Build for today's requirements, refactor when needs change
**KISS (Keep It Simple, Stupid):**
**CRITICAL:** Code maintainability always prevail
- Prefer simple(simple to maintain), straightforward solutions over complex, clever ones
- Complexity should be justified by actual requirements, not theoretical flexibility
- Simple code is easier to test, maintain, and debug
**Separation of Concerns:**
- Divide program functionality into distinct sections with minimal overlap
- Each concern should be isolated in its own module or layer
**Composition Over Inheritance:**
- Favor object composition and delegation over class inheritance for code reuse
- Composition is more flexible and easier to test
- Use interfaces/traits for polymorphism instead of deep inheritance hierarchies
**Principle of Least Astonishment:**
- Code should behave in ways that users and maintainers naturally expect
- Avoid surprising or counterintuitive behavior
- Follow established conventions and patterns
**User Experience vs Maintainability:**
- Both user experience AND code maintainability matter
- When they conflict, **prefer maintainable code** that can evolve
- Poor UX from clean code can be fixed; poor code from UX pressure becomes tech debt
- Maintainability enables future UX improvements
- Never sacrifice code quality for short-term UX gains
### Related Principles
- Architectural Patterns — Testability-First Design @architectural-pattern.md
- Code Organization Principles @code-organization-principles.md
- Documentation Principles @documentation-principles.md
- Accessibility Principles @accessibility-principles.md
@@ -0,0 +1,82 @@
---
trigger: model_decision
description: When serializing, deserializing, or validating data formats (JSON, XML, YAML, Protocol Buffers, MessagePack, etc.)
---
## Data Serialization and Interchange Principles
### Validate at System Boundaries
**All data entering system must be validated:**
- API requests, file uploads, message queue messages
- Validate type, format, range, required fields
- Fail fast on invalid data (don't process partially valid data)
- Return clear validation errors to client
### Handle Encoding Explicitly
**Default to UTF-8:**
- UTF-8 for all text data (API responses, file contents, database strings)
- Specify encoding explicitly when reading/writing files
- Handle encoding errors gracefully (replacement characters or error)
**Encoding errors:**
- Invalid UTF-8 sequences (malformed bytes)
- Mixing encodings (reading UTF-8 as ISO-8859-1)
- Always validate and normalize encoding
### Serialization Format Selection
**JSON:**
- Human-readable, widely supported, language-agnostic
- Good for: APIs, configuration files, web applications
- Limitations: No binary support, larger size than binary formats
**Protocol Buffers:**
- Compact binary format, fast serialization/deserialization
- Schema evolution (backward/forward compatibility)
- Good for: Internal microservices, high-throughput systems
- Limitations: Not human-readable, requires schema definition
**MessagePack:**
- Binary JSON-like format, faster and more compact than JSON
- Good for: Internal APIs, when JSON too slow but readability still desired
- Limitations: Less widely supported than JSON
**XML:**
- Verbose, legacy systems, document-oriented
- Good for: Enterprise systems, SOAP APIs, RSS/Atom feeds
- Limitations: Verbosity, complexity, security issues (XXE attacks)
**YAML:**
- Human-friendly, good for configuration files
- Good for: Config files, Infrastructure as Code (Kubernetes, CI/CD)
- Limitations: Complex parsing, performance, security issues (arbitrary code execution)
### Security Considerations
**Validate before deserialization:**
- Prevent deserialization attacks (arbitrary code execution)
- Set size limits on payloads (prevent memory exhaustion)
- Whitelist allowed types/classes for deserialization
**Disable dangerous features:**
- XML: Disable external entity processing (XXE prevention)
- YAML: Disable unsafe constructors
- Python pickle: Never deserialize untrusted data
### Related Principles
- Error Handling Principles @error-handling-principles.md
- Security Mandate @security-mandate.md
- Security Principles @security-principles.md
- API Design Principles @api-design-principles.md
@@ -0,0 +1,70 @@
---
trigger: model_decision
description: When designing database schemas, writing migrations, creating queries, or working with transaction boundaries
---
## Database Design Principles
### Schema Design
**Normalization:**
- Start with 3NF (Third Normal Form) and denormalize only when measured performance requires it
- Each table should represent a single entity
- Avoid storing derived/calculated data unless caching is explicitly needed
**Naming Conventions:**
- Tables: plural, snake_case (e.g., `users`, `task_assignments`)
- Columns: singular, snake_case (e.g., `created_at`, `user_id`)
- Foreign keys: `{referenced_table_singular}_id` (e.g., `user_id`, `task_id`)
- Indexes: `idx_{table}_{columns}` (e.g., `idx_users_email`)
- Constraints: `{type}_{table}_{columns}` (e.g., `uq_users_email`, `fk_tasks_user_id`)
**Required columns for all tables:**
- `id` — primary key (UUID preferred, auto-increment acceptable)
- `created_at` — timestamp, set on insert, never updated
- `updated_at` — timestamp, updated on every modification
### Migrations
**Safety Rules:**
- **Never drop columns in production** without a deprecation period
- **Never rename columns directly** — add new, migrate data, drop old
- **Always make migrations reversible** — include both up and down
- **Test migrations on a copy of production data** before applying
**Migration Strategy:**
1. Additive changes first (add column, add table)
2. Backfill data
3. Update application code
4. Remove old columns/tables in a future migration
### Queries
**Performance:**
- **Always use parameterized queries** (never string concatenation)
- **Index columns** used in WHERE, JOIN, and ORDER BY clauses
- **Avoid SELECT \*** — specify only needed columns
- **Watch for N+1 queries** — use JOINs or batch loading
- **Set query timeouts** to prevent long-running queries from blocking
**Transactions:**
- Use transactions for operations that modify multiple rows/tables
- Keep transactions as short as possible
- Handle deadlocks with retry logic
- Never hold transactions open during user interaction or external API calls
### Database Design Checklist
- [ ] Schema follows naming conventions?
- [ ] All tables have `id`, `created_at`, `updated_at`?
- [ ] Foreign keys have proper constraints and indexes?
- [ ] Queries are parameterized (no SQL injection)?
- [ ] Indexes exist for frequent query patterns?
- [ ] Migrations are reversible?
- [ ] Transactions are short and focused?
- [ ] N+1 queries avoided?
### Related Principles
- Security Principles @security-principles.md (SQL injection prevention)
- Performance Optimization Principles @performance-optimization-principles.md (query performance)
- Error Handling Principles @error-handling-principles.md (transaction error handling)
@@ -0,0 +1,64 @@
---
trigger: model_decision
description: When managing project dependencies, configuring package files, or organizing module imports
---
## Dependency Management Principles
### Version Pinning
**Production:** Pin exact versions (1.2.3, not ^1.2.0)
- Prevents supply chain attacks
- Prevents unexpected breakage from patch updates
- Ensures reproducible builds
**Use lock files:**
- `package-lock.json` (Node.js / npm)
- `pnpm-lock.yaml` (Node.js / pnpm)
- `yarn.lock` (Node.js / yarn)
- `Cargo.lock` (Rust)
- `go.sum` (Go)
- `pubspec.lock` (Flutter / Dart)
- `requirements.txt` / `poetry.lock` (Python)
### Minimize Dependencies
**Every dependency is a liability:**
- Potential security vulnerability
- Increased build time and artifact size
- Maintenance burden (updates, compatibility)
**Ask before adding dependency:**
- "Can I implement this in 50 lines?"
- "Is this functionality critical?"
- "Is this dependency actively maintained?"
- "Is this the latest stable version?"
### Organize Imports
**Grouping:**
1. Standard library
2. External dependencies
3. Internal modules
**Sorting:** Alphabetical within groups
**Cleanup:** Remove unused imports (use linter/formatter)
### Dependency Management Checklist
- [ ] Are production dependencies pinned to exact versions?
- [ ] Is the lock file committed to version control?
- [ ] Can each dependency be justified (not implementable in <50 lines)?
- [ ] Are all dependencies actively maintained and on latest stable versions?
- [ ] Are imports organized by group (stdlib → external → internal)?
- [ ] Are unused imports removed?
### Related Principles
- Security Mandate @security-mandate.md
- Security Principles @security-principles.md
+31
View File
@@ -0,0 +1,31 @@
---
trigger: always_on
---
## Documentation Principles
### Self-Documenting Code
**Clear naming reduces need for comments:**
- Code shows WHAT is happening
- Comments explain WHY it's done this way
**When to comment:**
- Complex business logic deserves explanation
- Non-obvious algorithms (explain approach)
- Workarounds for bugs (link to issue tracker)
- Performance optimizations (explain trade-offs)
### Documentation Levels
1. **Inline comments:** Explain WHY for complex code
2. **Function/method docs:** API contract (parameters, returns, errors)
3. **Module/package docs:** High-level purpose and usage
4. **README:** Setup, usage, examples
5. **Architecture docs:** System design, component interactions, use valid mermaid diagram
### Related Principles
- Core Design Principles @core-design-principles.md
- Code Organization Principles @code-organization-principles.md
@@ -0,0 +1,73 @@
---
trigger: model_decision
description: When implementing error handling, working with error types, or designing error recovery strategies (validation errors, business rule violations, infrastructure failures, resource cleanup)
---
## Error Handling Principles
**1. Never Fail Silently:**
- All errors must be handled explicitly (no empty catch blocks)
- If you catch an error, do something with it (log, return, transform, retry)
**2. Fail Fast:**
- Detect and report errors as early as possible
- Validate at system boundaries before processing
- Don't process invalid data hoping it'll work out
**3. Provide Context:**
- Include error codes, correlation IDs, actionable messages
- Enough information for debugging without exposing sensitive details
- Example: "Database query failed (correlation-id: abc-123)" not "SELECT * FROM users WHERE..."
**4. Separate Concerns:**
- Different handlers for different error types
- Business errors ≠ technical errors ≠ security errors
**5. Resource Cleanup:**
- Always clean up in error scenarios (close files, release connections, unlock resources)
- Use language-appropriate patterns (defer, finally, RAII, context managers)
**6. No Information Leakage:**
- Sanitize error messages for external consumption
- Don't expose stack traces, SQL queries, file paths, internal structure to users
- Log full details internally, show generic message externally
### Application Error Object (Internal/Log Format)
```
{
"status": "error",
"code": "VALIDATION_ERROR",
"message": "User-friendly error message",
"correlationId": "uuid-for-tracking",
"details": {
"field": "email",
"issue": "Invalid email format",
"provided": "invalid-email",
"expected": "valid email format ([email protected])"
}
}
```
### Error Handling Checklist
- [ ] Are all error paths explicitly handled (no empty catch blocks)?
- [ ] Do errors include correlation IDs for debugging?
- [ ] Are sensitive details sanitized before returning to client?
- [ ] Are resources cleaned up in all error scenarios?
- [ ] Are errors logged at appropriate levels (warn for 4xx, error for 5xx)?
- [ ] Are error tests written (negative test cases)?
- [ ] Is error handling consistent across application?
### Related Principles
- API Design Principles @api-design-principles.md - API Error Response Format section
- Logging and Observability Mandate @logging-and-observability-mandate.md
- Security Mandate @security-mandate.md
- Security Principles @security-principles.md
- Testing Strategy @testing-strategy.md
- Concurrency and Threading Mandate @concurrency-and-threading-mandate.md
+130
View File
@@ -0,0 +1,130 @@
---
trigger: model_decision
description: >
ONLY when feature flags are explicitly required by the PRD, technical
architecture document, or tech stack specification. Do NOT load this file
or introduce feature flags unless the requirement document explicitly calls
for gradual rollout, A/B testing, kill switches, or feature entitlement gating.
---
## Feature Flags Principles
> [!CAUTION]
> **Do NOT implement feature flags unless explicitly required.**
>
> Feature flags add real operational complexity: flag evaluation infrastructure,
> lifecycle management, and a multiplicative increase in test permutations.
> The cost is non-trivial for solo developers or simple projects.
>
> Only introduce feature flags when the **PRD** or **technical architecture document**
> explicitly specifies one of:
> - Gradual / percentage rollout of a risky feature
> - A/B testing or experimentation
> - Emergency kill switches for production code paths
> - Feature entitlement gating by user tier or permission
>
> If none of these are specified, deploy code directly. Do not introduce flags speculatively.
---
### When to Use Feature Flags
Feature flags decouple **deployment** from **release**. Code ships to production but stays
dormant until the flag enables it. This is valuable for:
| Use case | Flag type | Example |
|----------|-----------|---------|
| Gradual rollout | Release flag | `new-checkout-flow: 0→5→25→100%` |
| Emergency kill switch | Ops flag | `use-legacy-payment-provider` |
| A/B test / experiment | Experiment flag | `button-color-blue-vs-green` |
| Tier / permission gating | Permission flag | `pro-tier-analytics` |
---
### Infrastructure Requirements
> [!IMPORTANT]
> The flag evaluation backend **must be specified in the technical architecture document**
> before implementation begins. Do NOT choose a provider independently — ask the user
> which infrastructure to use.
| Approach | When to use | Notes |
|----------|-------------|-------|
| **Managed SaaS** | Teams > 5, multi-environment, complex targeting | LaunchDarkly, Flagsmith, Unleash Cloud |
| **Self-hosted** | Full control, no SaaS dependency | Unleash (OSS), Flagsmith (OSS) |
| **Firebase Remote Config** | Mobile apps in the Firebase ecosystem | Firebase SDK required |
| **Static config (YAML / env)** | Solo dev, simple on/off, single environment | Loaded at startup; no runtime targeting |
Flag configuration is infrastructure — it must be provisioned before code references it.
---
### Flag Evaluation Rules
- **Evaluate server-side** for security-sensitive features (never trust the client)
- **Evaluate at request boundary** — never inside pure business logic functions
- **Default to disabled** — if the flag service is unreachable, the flag is off (fail closed)
- **Wrap flag checks once** — in a service method or middleware, not scattered across business logic
**Pattern:**
```
// ✅ Flag evaluation at the boundary (handler or service entry point)
func (s *Service) CreateOrder(ctx context.Context, req Request) (Response, error) {
useNewPricing := s.flags.IsEnabled(ctx, "new-pricing-engine", req.UserID)
if useNewPricing {
return s.handleWithNewPricing(ctx, req)
}
return s.handleWithLegacyPricing(ctx, req)
}
// ❌ Flag evaluation buried inside business logic
func calculateDiscount(items []Item) float64 {
if flags.IsEnabled("new-discount-rules") { ... } // NO
}
```
---
### Flag Lifecycle Rules
Flags are temporary by design. Flag debt accumulates fast and becomes a maintenance burden.
- Every flag **must have an owner** (the team or developer responsible for its lifecycle)
- Every **release flag** has a **maximum lifespan of 90 days**
- When a release flag reaches 100% rollout, **create a ticket immediately** to remove it
- **Ops (kill switch) flags** can be permanent but must be documented as such
- **Experiment flags** must be removed when the experiment concludes
- Review all flags in sprint planning — any flag past its expiry date is tech debt
---
### Testing with Feature Flags
Feature flags multiply test permutations. Keep tests tractable:
- Unit tests test **each branch independently** (flag on + flag off)
- Integration tests default the flag to its **production-expected state** (usually fully on or off)
- Do not write tests that enumerate every possible flag combination
- Use a test-only flag override mechanism (e.g., `flags.Override(ctx, "flag", true)`) — never
branch test logic on environment variables
---
### CI/CD Checklist (Feature Flags)
- [ ] Flag infrastructure specified in technical architecture document?
- [ ] Flag backend provisioned and accessible by the application?
- [ ] Every flag has an owner and expiry date set?
- [ ] Flag evaluation is server-side for security-sensitive paths?
- [ ] Default state is `disabled` (fail closed)?
- [ ] Unit tests cover both flag-on and flag-off code paths?
- [ ] Removal ticket created once flag reaches 100% rollout?
---
### Related Principles
- CI/CD Principles @ci-cd-principles.md (deployment vs release concept)
- Architectural Patterns — Testability-First Design @architectural-pattern.md
- Core Design Principles @core-design-principles.md (YAGNI)
- Security Mandate @security-mandate.md (server-side evaluation)
@@ -0,0 +1,871 @@
---
trigger: model_decision
description: When writing Flutter or Dart code, working on mobile UI, using Riverpod for state management, or building Flutter widgets
---
## Flutter Idioms and Patterns (Riverpod 3)
### Core Philosophy
Flutter is a UI toolkit first — performance is a first-class concern. `const` widgets and immutable data keep the render tree efficient. **Riverpod 3** is the canonical state management solution: compile-safe, testable without `BuildContext`, no implicit global state, and with automatic retry and pause/resume built in.
**Code generation is mandatory.** All providers must use `@riverpod` / `@Riverpod(keepAlive: true)` annotations with `riverpod_generator` and `build_runner`. This catches type errors, missing overrides, and broken provider graphs at compile time — bugs are caught before they reach users.
**Required dependencies:**
```yaml
# pubspec.yaml
dependencies:
flutter_riverpod: 3.2.1
riverpod_annotation: 4.0.2
dev_dependencies:
riverpod_generator: 4.0.3
build_runner: # latest
riverpod_lint: # latest
```
> **Scope:** This file covers Flutter/Dart *coding idioms*. For file and folder layout, see `project-structure-flutter-mobile.md`. For test naming, see `testing-strategy.md`. For general error handling principles, see `error-handling-principles.md`.
---
### `const` Constructors — Everywhere
Make every widget `const` when possible. `const` widgets are created once and never rebuilt unless their inputs change — this is Flutter's most impactful performance optimization.
```dart
// ✅ const constructor — widget is rebuild-safe
class TaskCard extends StatelessWidget {
const TaskCard({super.key, required this.task});
final Task task;
// ...
}
// Usage — compile-time constant
const TaskCard(task: myTask)
// ❌ Missing const — rebuilt on every parent rebuild
TaskCard(task: myTask)
```
**Rules:**
- Every `StatelessWidget` that has no mutable state must have a `const` constructor
- Pass `const` keyword at the call site, not just the definition
- Lint rule `prefer_const_constructors` must be enabled in `analysis_options.yaml`
---
### Widget Decomposition
Large `build` methods are the primary source of performance problems and unmaintainable UI code.
1. **Extract a new widget when a subtree has distinct responsibilities**
```dart
// ❌ Everything in one build method
@override
Widget build(BuildContext context) {
return Column(children: [
// 30 lines of header...
// 50 lines of list...
// 20 lines of footer...
]);
}
// ✅ Each subtree is a named widget with a const constructor
@override
Widget build(BuildContext context) {
return Column(children: [
const TaskHeader(),
const TaskList(),
const TaskFooter(),
]);
}
```
2. **Never use builder methods (`_buildHeader()`) as a substitute for extracting widgets**
- Builder methods do not benefit from `const` and always rerun on parent rebuild
- Extract a proper `StatelessWidget` or `ConsumerWidget` instead
3. **Keep `build` methods under ~30 lines** — if longer, decompose
---
### Immutable Data with `freezed`
All domain models must be immutable. Use the `freezed` package for:
- Immutable value objects with `copyWith`
- Union/sealed types (loading, success, error states)
- Generated `==`, `hashCode`, and `toString`
```dart
// task/models/task.dart
@freezed
class Task with _$Task {
const factory Task({
required String id,
required String title,
@Default(TaskStatus.pending) TaskStatus status,
DateTime? dueDate,
}) = _Task;
factory Task.fromJson(Map<String, dynamic> json) => _$TaskFromJson(json);
}
// Usage — immutable update via copyWith
final updated = task.copyWith(status: TaskStatus.done);
// ❌ Never mutate a model directly
task.status = TaskStatus.done; // compile error — field is final
```
**Rules:**
- All domain models use `@freezed`
- Never expose mutable fields on domain models
- Run `dart run build_runner build` after changing freezed models
---
### Provider Decision Tree
Use this tree to pick the correct provider pattern. **Follow it top-to-bottom — the first match wins.**
```
Does the provider have side-effects (create/update/delete)? ──> YES ──┐
│ │
NO │
│ Is it async?
│ │ │
Is it async? YES NO
│ │ │ │
YES NO ▼ ▼
│ │ @riverpod @riverpod
▼ ▼ class class
@riverpod @riverpod (AsyncNotifier) (Notifier)
function function
(FutureProvider) (Provider)
```
**Decision summary:**
| Has side-effects? | Async? | Pattern | Provider type generated |
|---|---|---|---|
| No | No | `@riverpod` function | `Provider` |
| No | Yes | `@riverpod` async function | `FutureProvider` |
| No | Stream | `@riverpod` `Stream` function | `StreamProvider` |
| Yes | No | `@riverpod` class | `NotifierProvider` |
| Yes | Yes | `@riverpod` class with `Future` build | `AsyncNotifierProvider` |
| Yes | Stream | `@riverpod` class with `Stream` build | `StreamNotifierProvider` |
---
### Riverpod — State Management
**Riverpod 3 is the only state management solution** used in this project. Do not introduce BLoC, Cubit, Provider (package:provider), or GetX.
> For file layout of state/ directories, see `project-structure-flutter-mobile.md`.
#### App Entry Point — `ProviderScope`
Every Flutter app using Riverpod must wrap the root widget in `ProviderScope`. This creates the `ProviderContainer` that powers all providers in the widget tree.
```dart
// main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
```
**Rules:**
- Exactly one `ProviderScope` at the app root — never nest `ProviderScope` widgets
- Use `overrides:` parameter only in tests (see Testing section)
- For retry configuration, pass `retry:` to the root `ProviderScope` (see Runtime Behaviors)
#### Provider Definition — Class-Based (Side-Effects)
Use class-based providers when the provider needs methods to modify state.
```dart
// Synchronous notifier — e.g., a filter or toggle
@riverpod
class TaskFilter extends _$TaskFilter {
@override
TaskFilterState build() {
return const TaskFilterState();
}
void setStatus(TaskStatus? status) {
state = state.copyWith(status: status);
}
void toggleShowCompleted() {
state = state.copyWith(showCompleted: !state.showCompleted);
}
}
```
```dart
// Async notifier — e.g., CRUD operations
@riverpod
class TaskList extends _$TaskList {
@override
Future<List<Task>> build() async {
return ref.watch(taskRepositoryProvider).getTasks();
}
Future<void> addTask(CreateTaskRequest request) async {
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
final repo = ref.read(taskRepositoryProvider);
await repo.createTask(request);
// REQUIRED: check ref.mounted after every await
if (!ref.mounted) return state.requireValue;
return repo.getTasks();
});
}
Future<void> deleteTask(String id) async {
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
final repo = ref.read(taskRepositoryProvider);
await repo.deleteTask(id);
if (!ref.mounted) return state.requireValue;
return repo.getTasks();
});
}
}
```
#### Provider Definition — Functional (Read-Only / Computed)
Use functional providers for derived values with no side-effects.
```dart
// Computed value — filtered task list
@riverpod
List<Task> filteredTasks(Ref ref) {
final tasks = ref.watch(taskListProvider).valueOrNull ?? [];
final filter = ref.watch(taskFilterProvider);
return tasks.where((t) => filter.matches(t)).toList();
}
// Async one-shot read — e.g., fetch a single task
@riverpod
Future<Task> taskDetail(Ref ref, String id) async {
return ref.watch(taskRepositoryProvider).getById(id);
}
// Stream — real-time data
@riverpod
Stream<List<Task>> taskStream(Ref ref) {
return ref.watch(taskRepositoryProvider).watchAll();
}
```
**Family providers (parameterized):** When a provider takes extra arguments beyond `Ref`, Riverpod creates a *family* — each unique argument combination gets its own independent provider instance with its own cache and disposal. With code generation (which we mandate), **any number of parameters** are supported, including named, optional, and default values. Constraints:
- All parameters must implement `==` and `hashCode` (primitives, `freezed` models, Dart records all work)
- Each family member is disposed independently when no listener remains
```dart
// ✅ Family with a single key
@riverpod
Future<Task> taskDetail(Ref ref, String id) async {
return ref.watch(taskRepositoryProvider).getById(id);
}
// ✅ Multiple parameters — fully supported with code generation
@riverpod
Future<List<Task>> projectTasks(Ref ref, String projectId, {TaskStatus? status}) async {
return ref.watch(taskRepositoryProvider).getByProject(projectId, status: status);
}
// Usage: ref.watch(projectTasksProvider('proj-1', status: TaskStatus.done))
```
#### `ref.watch` vs `ref.read`
```dart
// ✅ ref.watch — subscribes to changes, use inside build() or widget build
final tasks = ref.watch(taskListProvider);
// ✅ ref.read — one-time read, use inside event handlers / notifier actions
Future<void> onSubmit() async {
await ref.read(taskListProvider.notifier).addTask(request);
}
// ❌ Never use ref.watch inside async functions or event handlers
Future<void> onSubmit() async {
final tasks = ref.watch(taskListProvider); // WRONG — causes errors
}
```
#### `Ref.mounted` — Mandatory After Awaits
Riverpod 3 throws if you interact with a disposed `Ref` or `Notifier`. **Always check `ref.mounted` after any `await` in a notifier.**
```dart
Future<void> updateTask(Task task) async {
final repo = ref.read(taskRepositoryProvider);
await repo.update(task);
// REQUIRED: provider may have been disposed during await
if (!ref.mounted) return;
state = await AsyncValue.guard(() => repo.getTasks());
}
```
#### Auto-Dispose and `keepAlive`
```dart
// ✅ autoDispose is the DEFAULT with code generation (@riverpod)
// Provider is disposed when no consumers are listening
@riverpod
Future<Task> taskDetail(Ref ref, String id) async {
return ref.watch(taskRepositoryProvider).getById(id);
}
// ✅ Opt into keepAlive explicitly for app-wide, long-lived state
@Riverpod(keepAlive: true)
class AuthState extends _$AuthState {
@override
Future<User?> build() async {
return ref.watch(authRepositoryProvider).getCurrentUser();
}
}
// ✅ Repositories should also be keepAlive — they hold connection state and
// should not be re-initialized every time a screen rebuilds
@Riverpod(keepAlive: true)
TaskRepository taskRepository(Ref ref) {
return TaskRepositoryImpl(apiClient: ref.watch(apiClientProvider));
}
// ❌ Do not set keepAlive: false — that is the default
// ❌ Do not set keepAlive: true for screen-scoped state
```
#### Repository Interface Pattern
All data access goes through an abstract repository interface. This is the Flutter expression of the Testability-First architecture (see `architectural-pattern.md`).
```dart
// repository/task_repository.dart — Abstract interface (contract)
abstract class TaskRepository {
Future<List<Task>> getTasks();
Future<Task> getById(String id);
Future<void> createTask(CreateTaskRequest request);
Future<void> deleteTask(String id);
}
```
```dart
// repository/task_repository_impl.dart — Production adapter
class TaskRepositoryImpl implements TaskRepository {
const TaskRepositoryImpl({required this.apiClient});
final ApiClient apiClient;
@override
Future<List<Task>> getTasks() async {
final response = await apiClient.get('/tasks');
return (response.data as List)
.map((e) => Task.fromJson(e as Map<String, dynamic>))
.toList();
}
@override
Future<Task> getById(String id) async {
final response = await apiClient.get('/tasks/$id');
return Task.fromJson(response.data as Map<String, dynamic>);
}
@override
Future<void> createTask(CreateTaskRequest request) async {
await apiClient.post('/tasks', data: request.toJson());
}
@override
Future<void> deleteTask(String id) async {
await apiClient.delete('/tasks/$id');
}
}
```
```dart
// repository/task_repository_mock.dart — Test adapter
class MockTaskRepository implements TaskRepository {
final List<Task> _tasks = [];
@override
Future<List<Task>> getTasks() async => List.unmodifiable(_tasks);
@override
Future<Task> getById(String id) async =>
_tasks.firstWhere((t) => t.id == id);
@override
Future<void> createTask(CreateTaskRequest request) async {
_tasks.add(Task(id: 'mock-id', title: request.title));
}
@override
Future<void> deleteTask(String id) async {
_tasks.removeWhere((t) => t.id == id);
}
}
```
```dart
// Wiring — provider that the rest of the app depends on
@Riverpod(keepAlive: true)
TaskRepository taskRepository(Ref ref) {
return TaskRepositoryImpl(apiClient: ref.watch(apiClientProvider));
}
// In tests, override with mock:
// taskRepositoryProvider.overrideWith((_) => MockTaskRepository())
```
#### ConsumerWidget vs ConsumerStatefulWidget
```dart
// ✅ Prefer ConsumerWidget — stateless, simpler
class TaskListView extends ConsumerWidget {
const TaskListView({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final asyncTasks = ref.watch(taskListProvider);
return asyncTasks.when(
data: (tasks) => TaskListBody(tasks: tasks),
loading: () => const LoadingIndicator(),
error: (e, _) => ErrorView(error: e),
);
}
}
// Use ConsumerStatefulWidget only when local widget state + riverpod is needed
```
---
### Riverpod 3 — Runtime Behaviors
#### Automatic Retry
Riverpod 3 automatically retries providers that throw, using exponential backoff. This improves resilience against transient network failures.
```dart
// ✅ Default behavior — providers auto-retry on failure
// No action needed for standard use
// ✅ Disable retry for a specific provider when failure is non-transient
@Riverpod(retry: null)
Future<Config> appConfig(Ref ref) async {
return ref.watch(configRepositoryProvider).load();
}
// ✅ Disable globally via ProviderScope
ProviderScope(
retry: (_, __) => null, // disable for all providers
child: const MyApp(),
)
```
**Rules:**
- Leave auto-retry enabled for network/IO operations (transient failures)
- Disable for operations where retry is unsafe (non-idempotent writes) or pointless (validation errors)
#### Pause / Resume (Out-of-View Providers)
Riverpod 3 automatically **pauses** provider listeners when the consuming widget is no longer visible. When the widget becomes visible again, listeners resume.
This is automatic — no action needed. Be aware of it when debugging.
#### `ProviderException` Wrapping
When a provider fails, reading it in Riverpod 3 throws a `ProviderException` wrapping the original error, not the raw exception.
```dart
// ✅ Catch ProviderException when reading providers that may fail
try {
final value = container.read(myProvider);
} on ProviderException catch (e) {
// e.exception contains the original error
// e.provider contains which provider failed
}
```
```dart
// ✅ Assert on provider failure in tests
expect(
() => container.read(myProvider),
throwsA(isA<ProviderException>()),
);
```
#### State Change Detection (`==`)
Riverpod 3 uses the `==` operator (not `identical`) to determine if state changed and rebuilds are needed. This means:
- `freezed` models work correctly out of the box (generated `==`)
- Custom models **must** implement `==` / `hashCode` or use `freezed`
- Override `updateShouldNotify` on a `Notifier` for custom comparison logic
---
### Async Patterns
1. **Always handle all three `AsyncValue` states: data, loading, error**
```dart
// ✅ Exhaustive
asyncValue.when(
data: (data) => DataWidget(data: data),
loading: () => const CircularProgressIndicator(),
error: (err, stack) => ErrorText(err.toString()),
);
```
2. **Surfacing notifier errors to UI** — use `when(error:...)` for exhaustive handling; use `hasError` only for conditional checks alongside a separate data display:
```dart
// ✅ Exhaustive — covers all states, preferred for full-screen states
asyncTasks.when(
data: (tasks) => TaskListBody(tasks: tasks),
loading: () => const LoadingIndicator(),
error: (e, _) => ErrorView(error: e),
);
// ✅ Conditional — show inline error banner while keeping stale data visible
if (asyncTasks.hasError) {
// show snackbar or inline error
}
final tasks = asyncTasks.valueOrNull ?? const [];
```
3. **Use safe `AsyncValue` accessors to prevent runtime crashes**
```dart
// ✅ Safe — returns null if state is loading or error
final tasks = ref.watch(taskListProvider).valueOrNull;
// ⚠️ Unsafe — throws StateError if state is not AsyncData
// Only use when you have already confirmed the state is loaded
final tasks = ref.watch(taskListProvider).requireValue;
```
4. **Use `AsyncValue.guard` inside notifier actions to wrap async calls**
- It catches exceptions and wraps them in `AsyncError` automatically
5. **Use `StreamProvider` for real-time data** — never poll manually with `Timer`
6. **Always check `ref.mounted` after `await`** — see `Ref.mounted` section above
7. **Force a provider to re-fetch with `ref.invalidate`**
```dart
// ✅ Invalidate from outside a notifier (e.g., after a form submit in a widget)
ref.invalidate(taskListProvider);
// The next watch/read will trigger a fresh build()
// ✅ Invalidate from inside a notifier after a mutation
Future<void> addTask(CreateTaskRequest request) async {
final repo = ref.read(taskRepositoryProvider);
await repo.createTask(request);
if (!ref.mounted) return;
ref.invalidateSelf(); // triggers build() to re-run and return fresh list
}
```
**Decision: `invalidateSelf()` vs manual state assignment**
```
After a mutation:
├── Need optimistic UI (instant visual update before server confirms)?
│ └── YES → Set state manually via AsyncValue.guard
│ (e.g., remove item from list immediately, then call API)
└── NO → ref.invalidateSelf() (simpler, always correct — re-runs build())
```
---
### Error Handling in Flutter/Riverpod
Define a typed exception hierarchy using sealed classes (Dart 3+). Map infrastructure exceptions to domain exceptions inside notifier actions.
```dart
// core/errors/app_exception.dart — Typed exception hierarchy
sealed class AppException implements Exception {
const AppException(this.message);
final String message;
@override
String toString() => message;
}
class NetworkException extends AppException {
const NetworkException(super.message, {this.statusCode});
final int? statusCode;
}
class ValidationException extends AppException {
const ValidationException(super.message, {required this.field});
final String field;
}
class NotFoundException extends AppException {
const NotFoundException(super.message);
}
```
```dart
// ✅ Map infrastructure exceptions to domain exceptions inside notifier actions
Future<void> addTask(CreateTaskRequest request) async {
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
try {
final repo = ref.read(taskRepositoryProvider);
await repo.createTask(request);
if (!ref.mounted) return state.requireValue;
return repo.getTasks();
} on DioException catch (e) {
// Transform infrastructure error → domain error
throw NetworkException(
'Failed to create task: ${e.message}',
statusCode: e.response?.statusCode,
);
}
});
}
```
```dart
// ✅ Handle typed errors in UI
asyncTasks.when(
data: (tasks) => TaskListBody(tasks: tasks),
loading: () => const LoadingIndicator(),
error: (error, _) => switch (error) {
NetworkException() => ErrorView(message: 'Network error: ${error.message}'),
NotFoundException() => const ErrorView(message: 'Not found'),
_ => ErrorView(message: 'Unexpected error: $error'),
},
);
```
**Rules:**
- All custom exceptions extend `AppException` (sealed class)
- Infrastructure exceptions (`DioException`, `SocketException`) are caught and re-thrown as domain exceptions
- Never expose infrastructure types (Dio, HTTP status codes) to the UI layer
- Use exhaustive `switch` on the sealed class in error display
- See `error-handling-principles.md` for general error handling guidance
---
### Navigation with `go_router`
**`go_router` is the canonical navigation library.**
```dart
// core/router/app_router.dart
@riverpod
GoRouter appRouter(Ref ref) {
return GoRouter(
initialLocation: '/tasks',
routes: [
GoRoute(path: '/tasks', builder: (_, __) => const TaskListView()),
GoRoute(
path: '/tasks/:id',
builder: (_, state) => TaskDetailView(
// state.pathParameters['id'] is guaranteed non-null by the :id
// route pattern — acceptable use of ! in route infrastructure code
id: state.pathParameters['id']!,
),
),
],
);
}
// Navigate — always by path, never by widget reference
context.go('/tasks/$taskId');
context.push('/tasks/new'); // push adds to the back stack
```
---
### Dart Language Idioms
1. **Null safety — use `?.`, `??`, and `??=` idiomatically**
```dart
final city = user?.address?.city ?? 'Unknown';
cache ??= await compute(); // assign only if null
```
2. **Use `late` only for fields initialized before first use that cannot be `final`**
- Prefer `final` fields initialized in the constructor
- `late` without initialization is an unsafe nullable escape hatch
3. **Extension methods for adding behaviour to types you don't own**
```dart
extension TaskStatusLabel on TaskStatus {
String get label => switch (this) {
TaskStatus.pending => 'Pending',
TaskStatus.done => 'Done',
};
}
```
4. **Use `switch` expressions (Dart 3+) for exhaustive pattern matching**
```dart
final label = status switch {
TaskStatus.pending => 'Pending',
TaskStatus.done => 'Done',
// Compiler error if a case is missing
};
```
5. **Avoid `dynamic` — it is the Dart equivalent of TypeScript's `any`**
---
### Testing
> Test naming and pyramid proportions are defined in `testing-strategy.md`. This section covers Flutter/Riverpod 3 test patterns.
#### Unit Test Providers with `ProviderContainer.test`
`ProviderContainer.test` creates an isolated container that **auto-disposes after the test** — no manual `addTearDown` needed.
```dart
test('addTask updates state', () async {
final container = ProviderContainer.test(overrides: [
taskRepositoryProvider.overrideWith((_) => MockTaskRepository()),
]);
await container.read(taskListProvider.notifier).addTask(request);
expect(container.read(taskListProvider).value, hasLength(1));
});
```
#### Override Only the `build` Method with `overrideWithBuild`
When you need to control initial state but keep the notifier's methods intact:
```dart
test('deleteTask removes item from pre-seeded list', () async {
final container = ProviderContainer.test(overrides: [
taskRepositoryProvider.overrideWith((_) => MockTaskRepository()),
taskListProvider.overrideWithBuild((ref, notifier) {
// Seed initial state — methods (addTask, deleteTask) still work
return Future.value([mockTask1, mockTask2]);
}),
]);
await container.read(taskListProvider.notifier).deleteTask(mockTask1.id);
expect(container.read(taskListProvider).value, hasLength(1));
});
```
#### Widget Tests with `ProviderScope`
```dart
testWidgets('shows task list', (tester) async {
await tester.pumpWidget(ProviderScope(
overrides: [
taskRepositoryProvider.overrideWith((_) => MockTaskRepository()),
],
child: const MaterialApp(home: TaskListView()),
));
expect(find.byType(TaskCard), findsWidgets);
});
```
#### Use `mockito` with `@GenerateNiceMocks` for Interface Mocks
Place the annotation on the library (top of the test file or on `main`) and add a `part` directive for the generated file:
```dart
// task_notifier_test.dart
@GenerateNiceMocks([MockSpec<TaskRepository>()])
library;
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'task_notifier_test.mocks.dart'; // generated by build_runner
void main() {
// tests use MockTaskRepository()
}
```
---
### Anti-Patterns — NEVER DO THIS
These are common mistakes agents reproduce from Riverpod 2 training data. **Every item below is a hard rule violation.**
| ❌ Anti-Pattern | ✅ Correct Pattern |
|---|---|
| `StateProvider` | `@riverpod` class with `Notifier` |
| `StateNotifierProvider` | `@riverpod` class with `Notifier` |
| `ChangeNotifierProvider` | `@riverpod` class with `Notifier` |
| `import 'package:riverpod/legacy.dart'` | Never import legacy APIs |
| Typed ref subclass (`TaskDetailRef ref`) | `Ref ref` — Riverpod 3 uses a single `Ref` type; typed subclasses were a Riverpod 2 codegen artifact that no longer exists |
| `ref.watch` inside async/event handler | `ref.read` for one-shot reads in handlers |
| Accessing `state`/`ref` after `await` without `ref.mounted` check | Always check `ref.mounted` after `await` |
| `ProviderContainer()` + `addTearDown(container.dispose)` | `ProviderContainer.test(overrides: [...])` |
| `Timer.periodic` for polling data | `StreamProvider` or `Stream` return |
| `keepAlive: false` in annotation | Omit — false is the default |
| Manual providers without `@riverpod` annotation | Always use code generation |
| Catching raw exceptions from provider reads | Catch `ProviderException` |
| `overrideWith((_) => MockNotifier())` when only initial state needs seeding | `overrideWithBuild(...)` — keeps notifier methods intact, only seeds initial state |
---
### Linting and Formatting
| Tool | Purpose | Config File |
| --- | --- | --- |
| `dart format` | Canonical formatting | — (built-in) |
| `flutter analyze` | Static analysis + lint | `analysis_options.yaml` |
| `riverpod_lint` | Riverpod-specific lint rules | `dev_dependencies` |
| `dart pub deps` | Dependency audit | — |
| `dart run build_runner build` | Generate provider code | — |
**Mandatory `analysis_options.yaml` settings (Dart 3+):**
```yaml
analyzer:
language:
strict-casts: true
strict-raw-types: true
errors:
invalid_assignment: error
plugins:
- riverpod_lint
linter:
rules:
- prefer_const_constructors
- prefer_const_declarations
- avoid_dynamic_calls
- avoid_print
- use_super_parameters
```
**After any code change involving providers, run:**
```bash
dart run build_runner build --delete-conflicting-outputs
flutter analyze
dart format .
```
**During active development, use `watch` mode to avoid re-running manually:**
```bash
dart run build_runner watch --delete-conflicting-outputs
```
---
### Related Principles
- Code Idioms and Conventions @code-idioms-and-conventions.md
- Project Structure — Flutter Mobile @project-structure-flutter-mobile.md
- Architectural Patterns — Testability-First Design @architectural-pattern.md
- Testing Strategy @testing-strategy.md
- Error Handling Principles @error-handling-principles.md
- Dependency Management Principles @dependency-management-principles.md
+88
View File
@@ -0,0 +1,88 @@
---
trigger: model_decision
description: When creating branches, committing code, managing PRs, or working with version control
---
## Git Workflow Principles
### Commit Messages — Conventional Commits
**Format:**
```
<type>(<scope>): <description>
[optional body]
[optional footer]
```
**Types:**
| Type | Purpose |
| ---------- | -------------------------------- |
| `feat` | New feature |
| `fix` | Bug fix |
| `docs` | Documentation only |
| `style` | Formatting, semicolons, etc. |
| `refactor` | Code change (no new feature/fix) |
| `test` | Adding or updating tests |
| `chore` | Maintenance, dependencies |
| `perf` | Performance improvement |
| `ci` | CI/CD configuration changes |
**Rules:**
- Description is imperative mood ("add" not "added", "fix" not "fixes")
- Scope matches the feature area (e.g., `task`, `auth`, `ui`)
- Description is concise (<72 characters)
- Body explains **why**, not what (the diff shows what)
### Branch Naming
**Format:** `<type>/<ticket-or-short-description>`
**Examples:**
```
feat/task-crud-api
fix/auth-token-expiry
refactor/storage-layer
chore/update-deps
```
**Rules:**
- Use lowercase with hyphens (kebab-case)
- Prefix matches commit type
- Keep branch names short but descriptive
### Commit Hygiene
- **One logical change per commit** — don't mix unrelated changes
- **Never commit broken tests** — all tests must pass before committing
- **Don't commit debug code** — remove console.log, print statements, TODO hacks
- **Don't commit secrets** — use `.gitignore` and environment variables
### PR Size Guidelines
- **Ideal:** <400 lines changed
- **Acceptable:** 400-800 lines
- **Too large:** >800 lines — split into smaller PRs
**Why:** Large PRs get rubber-stamped. Small PRs get thoughtful reviews.
### Merge Strategy
- **Feature branches → main:** Squash merge (clean history)
- **Release branches:** Merge commit (preserve history)
- **Hotfixes:** Cherry-pick to affected branches
### Git Workflow Checklist
- [ ] Branch named with correct type prefix?
- [ ] All commits follow conventional format?
- [ ] No debug code or secrets committed?
- [ ] All tests pass before committing?
- [ ] PR is <400 lines (or justified if larger)?
- [ ] Commit messages explain why, not just what?
### Related Principles
- Code Completion Mandate @code-completion-mandate.md
- Testing Strategy @testing-strategy.md
- Security Mandate @security-mandate.md
+315
View File
@@ -0,0 +1,315 @@
---
trigger: model_decision
description: When writing Go code, reviewing Go idioms, or working on a Go backend project
---
## Go Idioms and Patterns
### Core Philosophy
Go favors simplicity, explicitness, and readability. The language is intentionally small — resist the urge to import patterns from other languages. If it looks boring and obvious, it's probably idiomatic Go.
> **Scope:** This file covers Go-specific *coding idioms*. For file layout, see `project-structure-go-backend.md`. For test naming conventions, see `testing-strategy.md`. For logging library choice, see `logging-and-observability-principles.md`.
---
### Error Handling
1. **Always return errors — never panic in library or business code**
- `panic` is reserved for truly unrecoverable states (programmer errors, nil dereference)
- Use `recover` only at top-level goroutine boundaries (middleware, server startup)
2. **Wrap errors with context using `%w`**
```go
// ✅ Preserves the error chain for errors.Is / errors.As
return fmt.Errorf("creating task for user %s: %w", userID, err)
// ❌ Loses the error chain
return fmt.Errorf("creating task: %v", err)
```
3. **Use sentinel errors for expected branch conditions**
```go
// Define in errors.go
var ErrNotFound = errors.New("not found")
var ErrUnauthorized = errors.New("unauthorized")
// Caller checks with errors.Is
if errors.Is(err, ErrNotFound) {
// handle
}
```
4. **Use typed errors for rich domain errors**
```go
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}
// Caller unwraps with errors.As
var ve *ValidationError
if errors.As(err, &ve) {
// access ve.Field, ve.Message
}
```
5. **Handle errors at the right level** — propagate upward until you have enough context to act on them; don't swallow or re-wrap the same error twice.
---
### Interfaces
1. **Keep interfaces small — one or two methods is ideal**
```go
// ✅ Focused, composable
type Reader interface { Read(p []byte) (n int, err error) }
type Writer interface { Write(p []byte) (n int, err error) }
// ❌ Monolithic
type FileManager interface {
Read(...); Write(...); Delete(...); List(...); Stat(...)
}
```
2. **"Accept interfaces, return structs"**
- Function parameters: accept interfaces for flexibility and testability
- Return values: return concrete structs so callers can access all methods
3. **Define interfaces where they are *used*, not where they are *implemented***
```go
// ✅ Defined in the consumer package (task feature)
// task/storage.go
type Storage interface {
GetByID(ctx context.Context, id string) (*Task, error)
}
// postgres.go implements Storage — it does NOT define it
```
4. **Implicit satisfaction is a feature — don't use embedding to "implement" interfaces**
- Any type with the right method set satisfies an interface automatically
- No `implements` keyword needed or wanted
---
### Goroutines and Channels
> For general concurrency principles (race conditions, deadlocks, message passing), see `concurrency-and-threading-principles.md`. This section covers Go-specific mechanics.
1. **Always pass `context.Context` as the first parameter**
```go
// ✅
func (s *Service) GetTask(ctx context.Context, id string) (*Task, error)
// ❌ — no way to cancel or propagate deadlines
func (s *Service) GetTask(id string) (*Task, error)
```
2. **Never start a goroutine without knowing how it will stop**
```go
// ✅ Goroutine is bounded by context cancellation
go func() {
for {
select {
case <-ctx.Done():
return
case item := <-ch:
process(item)
}
}
}()
```
3. **Use `errgroup` for concurrent fan-out with error collection**
```go
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error { return fetchUsers(ctx) })
g.Go(func() error { return fetchOrders(ctx) })
if err := g.Wait(); err != nil { ... }
```
4. **Prefer channels for ownership transfer; mutexes for shared state**
- Channel: "I'm handing this data to you"
- Mutex: "We're both reading/writing this shared thing"
5. **Close channels from the sender, never the receiver**
---
### Naming Conventions
1. **Receiver names: short, consistent, and the first letter of the type**
```go
func (s *Service) Create(...) {} // ✅
func (svc *Service) Create(...) {} // ❌ — too verbose
func (self *Service) Create(...) {} // ❌ — not Go
```
2. **Package names: short, lowercase, no underscores, no plurals**
```go
package task // ✅
package tasks // ❌ plural
package task_service // ❌ underscore
```
3. **Acronyms follow Go conventions (all caps or all lowercase)**
```go
userID // ✅
userId // ❌
HTTPClient // ✅
HttpClient // ❌
```
4. **Unexported identifiers omit the type name** — if it's private, keep it terse
5. **Don't stutter** — `task.Task` is fine; `task.TaskService` is not
---
### Idiomatic Patterns
1. **Functional options for optional configuration**
```go
type Option func(*Service)
func WithTimeout(d time.Duration) Option {
return func(s *Service) { s.timeout = d }
}
func NewService(store Storage, opts ...Option) *Service {
s := &Service{store: store, timeout: 30 * time.Second}
for _, o := range opts { o(s) }
return s
}
```
2. **`defer` for cleanup — always use error-checked closures**
Every deferred cleanup call that returns an error MUST check and log the error.
Never use bare `defer X.Close()` — the discarded error hides resource leak failures.
```go
// ❌ NEVER: Error silently discarded
defer rows.Close()
// ✅ ALWAYS: Error-checked closure with structured logging
rows, err := db.QueryContext(ctx, query)
if err != nil { return fmt.Errorf("querying tasks: %w", err) }
defer func() {
if err := rows.Close(); err != nil {
slog.Warn("failed to close rows", "error", err, "operation", "ListTasks")
}
}()
```
**Transaction rollback:**
```go
// ❌ NEVER
defer tx.Rollback()
// ✅ ALWAYS: Guard against sql.ErrTxDone (already committed)
defer func() {
if err := tx.Rollback(); err != nil && !errors.Is(err, sql.ErrTxDone) {
slog.Error("failed to rollback transaction", "error", err, "operation", "CreateOrder")
}
}()
```
**HTTP response body:**
```go
// ❌ NEVER
defer resp.Body.Close()
// ✅ ALWAYS: Drain then close (prevents connection reuse issues)
defer func() {
if _, err := io.Copy(io.Discard, resp.Body); err != nil {
slog.Warn("failed to drain response body", "error", err)
}
if err := resp.Body.Close(); err != nil {
slog.Warn("failed to close response body", "error", err)
}
}()
```
3. **Avoid `init()` functions** — they run implicitly and make testing harder; prefer explicit initialization in `main` or constructors
4. **Prefer `struct` embedding over inheritance for code reuse**, but only when the embedded type truly represents an "is-a" relationship
5. **Use named return values only for documentation or `defer`-based cleanup** — never rely on naked returns in non-trivial functions
---
### Testing
> Test file naming and pyramid proportions are defined in `testing-strategy.md`. This section covers Go-specific tooling only.
1. **Table-driven tests are the default pattern**
```go
func TestCalculateDiscount(t *testing.T) {
tests := []struct {
name string
input float64
expected float64
wantErr bool
}{
{"zero items", 0, 0, false},
{"negative input", -1, 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := calculateDiscount(tt.input)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.expected, got)
})
}
}
```
2. **Use `testify` for assertions** (`require` for fatal assertions, `assert` for non-fatal)
3. **Run with the race detector in CI** — `go test -race ./...`
4. **Use `httptest.NewRecorder()` for HTTP handler tests** — no live server needed
5. **Test behaviour, not implementation** — assert on outputs and side effects, not internal field values
---
### Formatting and Static Analysis
All of the following **must pass with zero warnings/errors** before any commit. See `code-completion-mandate.md` for the full checklist.
| Tool | Purpose | Command |
| ----------------------- | ------------------------ | -------------------- |
| `gofumpt` / `goimports` | Canonical formatting | `gofumpt -l -w .` |
| `go vet` | Correctness checks | `go vet ./...` |
| `staticcheck` | Advanced static analysis | `staticcheck ./...` |
| `gosec` | Security scanning | `gosec -quiet ./...` |
| `golangci-lint` | Aggregated linter (CI) | `golangci-lint run` |
| `govulncheck` | Dependency CVE scanning | `govulncheck ./...` |
- Never disable a linter without a comment explaining why
- **`//nolint:errcheck` is NEVER acceptable.** If a function returns an error, handle it — even in `defer`. Use an error-checked closure (see § Idiomatic Patterns above). This is the #1 source of audit findings.
- Other `//nolint:` directives require a `// NOLINT:` rationale comment AND must be approved during code review
- Fast iteration during development: `go vet ./...` type-checks and catches correctness issues without producing binaries (analogous to `cargo check`) — reserve `golangci-lint` for pre-commit
> **Logging:** Never use `fmt.Println` or `log.Printf` in production service code — these produce unstructured output. Use `log/slog` (stdlib, Go 1.21+) or the project's chosen adapter. See `logging-and-observability-principles.md` for the required library and patterns.
---
### Related Principles
- Code Idioms and Conventions @code-idioms-and-conventions.md
- Project Structure — Go Backend @project-structure-go-backend.md
- Testing Strategy @testing-strategy.md
- Error Handling Principles @error-handling-principles.md
- Concurrency and Threading Principles @concurrency-and-threading-principles.md
- Logging and Observability Principles @logging-and-observability-principles.md
- Dependency Management Principles @dependency-management-principles.md
@@ -0,0 +1,58 @@
---
trigger: always_on
---
## Logging and Observability Mandate
### Observability Overview
Observability is built on **three pillars** — this mandate governs Logs. For Metrics and Health Checks, see Monitoring and Alerting Principles @monitoring-and-alerting-principles.md.
1. **Logs** — *What happened* (events, errors, state changes) — governed by this file
2. **Metrics** — *How much / how many* (quantitative measurements, RED/USE) — see `monitoring-and-alerting-principles.md`
3. **Traces** — *How did it happen* (request flow through system) — platform-level concern (OpenTelemetry, Jaeger)
### Universal Requirement: All Operations Must Be Logged
**Every operation entry point MUST include logging. No exceptions.**
**What constitutes an "operation" (mandatory logging):**
- API endpoints and request handlers
- Background jobs and queue workers
- Event handlers and message consumers
- Scheduled tasks and cron jobs
- CLI commands
- External service calls (to third-party APIs)
- Database transactions
**What is NOT an operation (no direct logging):**
- Pure business logic functions (called within operations)
- Utility and helper functions
- Data transformations and validators
**Minimum logging requirement (3 points):**
1. **Operation start:** Log at entry with context (correlationId, userId, operation name)
2. **Operation success:** Log completion with duration and result identifiers
3. **Operation failure:** Log error with full context (correlationId, error details, stack trace)
**Mandatory context in all logs:**
- `correlationId`: UUID for tracing across services
- `operation`: Clear operation name (e.g., "create_order", "process_payment")
- `duration`: Execution time in milliseconds
- `userId`: Actor who triggered the operation (when applicable)
- `error`: Full error context on failures
**Enforcement strategy:**
Prefer middleware, decorators, or framework interceptors for automatic operation logging rather than manual logging in every handler. This ensures coverage without code duplication.
**When implementing any operation entry point, you MUST add logging before proceeding with implementation.**
### Implementation Guide
For log levels, structured logging patterns, language-specific implementations, security considerations, and code examples, see Logging and Observability Principles @logging-and-observability-principles.md.
### Related Principles
- Logging and Observability Principles @logging-and-observability-principles.md
- Monitoring and Alerting Principles @monitoring-and-alerting-principles.md
- Error Handling Principles @error-handling-principles.md
- API Design Principles @api-design-principles.md
@@ -0,0 +1,361 @@
---
trigger: model_decision
description: When implementing logging, working with loggers, or setting up observability for operations (API handlers, database queries, background jobs, external API calls)
---
## Logging and Observability Principles
> **⚠️ Prerequisite:** All operations MUST be logged per Logging and Observability Mandate @logging-and-observability-mandate.md. This guide provides implementation patterns only.
### Logging Standards
#### Log Levels (Standard Priority)
Use consistent log levels across all services:
| Level | When to Use | Examples |
| --------- | --------------------------------------- | -------------------------------------------------------- |
| **TRACE** | Extremely detailed diagnostic info | Function entry/exit, variable states (dev only) |
| **DEBUG** | Detailed flow for debugging | Query execution, cache hits/misses, state transitions |
| **INFO** | General informational messages | Request started, task created, user logged in |
| **WARN** | Potentially harmful situations | Deprecated API usage, fallback triggered, retry attempt |
| **ERROR** | Error events that allow app to continue | Request failed, external API timeout, validation failure |
| **FATAL** | Severe errors causing shutdown | Database unreachable, critical config missing |
#### Logging Rules
**1. Every request/operation must log:**
```
// Start of operation
log.Info("creating task",
"correlationId", correlationID,
"userId", userID,
"title", task.Title,
)
// Success
log.Info("task created successfully",
"correlationId", correlationID,
"taskId", task.ID,
"duration", time.Since(start),
)
// Error
log.Error("failed to create task",
"correlationId", correlationID,
"error", err,
"userId", userID,
)
```
**2. Always include context:**
- `correlationId`: Trace requests across services (UUID)
- `userId`: Who triggered the action
- `duration`: Operation timing (milliseconds)
- `error`: Error details (if failed)
**3. Structured logging only** (no string formatting):
```
// ✅ Structured
log.Info("user login", "userId", userID, "ip", clientIP)
// ❌ String formatting
log.Info(fmt.Sprintf("User %s logged in from %s", userID, clientIP))
```
**4. Security - Never log:**
- Passwords or password hashes
- API keys or tokens
- Credit card numbers
- PII in production logs (email/phone only if necessary and sanitized)
- Full request/response bodies (unless DEBUG level in non-prod)
**5. Performance - Never log in hot paths:**
- Inside tight loops
- Per-item processing in batch operations (use summary instead)
- Synchronous logging in latency-critical paths
**Best Practice:** "Use logger middleware redaction (e.g., pino-redact, zap masking) rather than manual string manipulation."
#### Language-Specific Implementations
##### Go (using slog standard library)
```
import "log/slog"
// Configure logger
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo, // Production default
}))
// Usage
logger.Info("operation started",
"correlationId", correlationID,
"userId", userID,
)
logger.Error("operation failed",
"correlationId", correlationID,
"error", err,
"retryCount", retries,
)
```
##### TypeScript/Node.js (using pino)
```
import pino from 'pino';
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
});
logger.info({
correlationId,
userId,
duration: Date.now() - startTime,
}, 'task created successfully');
logger.error({
correlationId,
error: err.message,
stack: err.stack,
}, 'failed to create task');
```
##### Python (using structlog)
```python
import structlog
logger = structlog.get_logger()
logger.info("task_created",
correlation_id=correlation_id,
user_id=user_id,
task_id=task.id,
)
logger.error("task_creation_failed",
correlation_id=correlation_id,
error=str(err),
user_id=user_id,
)
```
##### Log Patterns by Operation Type
##### API Request/Response
```
// Request received
log.Info("request received",
"method", r.Method,
"path", r.URL.Path,
"correlationId", correlationID,
"userId", userID,
)
// Request completed
log.Info("request completed",
"correlationId", correlationID,
"status", statusCode,
"duration", duration,
)
```
##### Database Operations
```
// Query start (DEBUG level)
log.Debug("executing query",
"correlationId", correlationID,
"query", "SELECT * FROM tasks WHERE user_id = $1",
)
// Query success (DEBUG level)
log.Debug("query completed",
"correlationId", correlationID,
"rowsReturned", len(results),
"duration", duration,
)
// Query error (ERROR level)
log.Error("query failed",
"correlationId", correlationID,
"error", err,
"query", "SELECT * FROM tasks WHERE user_id = $1",
)
```
##### External API Calls
```
// Call start
log.Info("calling external API",
"correlationId", correlationID,
"service", "email-provider",
"endpoint", "/send",
)
// Retry (WARN level)
log.Warn("retrying external API call",
"correlationId", correlationID,
"service", "email-provider",
"attempt", retryCount,
"error", err,
)
// Circuit breaker open (WARN level)
log.Warn("circuit breaker opened",
"correlationId", correlationID,
"service", "email-provider",
"failureCount", failures,
)
```
##### Background Jobs
```
// Job start
log.Info("job started",
"jobId", jobID,
"jobType", "email-digest",
)
// Progress (INFO level - periodic, not per-item)
log.Info("job progress",
"jobId", jobID,
"processed", 1000,
"total", 5000,
"percentComplete", 20,
)
// Job complete
log.Info("job completed",
"jobId", jobID,
"duration", duration,
"itemsProcessed", count,
)
```
##### Error Scenarios
```
// Recoverable error (ERROR level)
log.Error("validation failed",
"correlationId", correlationID,
"userId", userID,
"error", "invalid email format",
"input", sanitizedInput, // Sanitized!
)
// Fatal error (FATAL level)
log.Fatal("critical dependency unavailable",
"error", err,
"dependency", "database",
"action", "shutting down",
)
```
#### Environment-Specific Configuration
| Environment | Level | Format | Destination |
| --------------- | ----- | ---------------- | ----------------------- |
| **Development** | DEBUG | Pretty (colored) | Console |
| **Staging** | INFO | JSON | Stdout → CloudWatch/GCP |
| **Production** | INFO | JSON | Stdout → CloudWatch/GCP |
**Configuration (Go example):**
```
func configureLogger() *slog.Logger {
var handler slog.Handler
level := slog.LevelInfo
if os.Getenv("ENV") == "development" {
level = slog.LevelDebug
handler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: level,
})
} else {
handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: level,
})
}
return slog.New(handler)
}
```
#### Testing Logs
**Unit tests:** Capture and assert on log output
```
// Go example
func TestUserLogin(t *testing.T) {
var buf bytes.Buffer
logger := slog.New(slog.NewJSONHandler(&buf, nil))
// Test operation
service := NewUserService(logger, mockStore)
err := service.Login(ctx, email, password)
// Assert logs
require.NoError(t, err)
logs := buf.String()
assert.Contains(t, logs, "user login successful")
assert.Contains(t, logs, email)
}
```
#### Monitoring Integration
**Correlation IDs:**
- Generate at ingress (API gateway, first handler)
- Propagate through all services
- Include in all logs, errors, and traces
- Format: UUID v4
**Log aggregation:**
- Ship to centralized system (CloudWatch, GCP Logs, Datadog)
- Index by: correlationId, userId, level, timestamp
- Alert on ERROR/FATAL patterns
- Dashboard: request rates, error rates, latency
#### Checklist for Every Feature
- [ ] All public operations log INFO on start
- [ ] All operations log INFO/ERROR on complete/failure
- [ ] All logs include correlationId
- [ ] No sensitive data in logs
- [ ] Structured logging (key-value pairs)
- [ ] Appropriate log level used
- [ ] Error logs include error details
- [ ] Performance-critical paths use DEBUG level
### Related Principles
- Logging and Observability Mandate @logging-and-observability-mandate.md
- Monitoring and Alerting Principles @monitoring-and-alerting-principles.md
- Error Handling Principles @error-handling-principles.md
- Security Mandate @security-mandate.md
- Security Principles @security-principles.md
- API Design Principles @api-design-principles.md
@@ -0,0 +1,86 @@
---
trigger: model_decision
description: When implementing health checks, metrics instrumentation, error tracking integration, or production observability code
---
## Monitoring and Alerting Principles
> **Scope:** This rule covers what the agent **implements in code**. Organizational
> concerns (SLO/SLI definitions, on-call rotation, alert escalation) are out of scope —
> those are team/org decisions, not code generation concerns.
### Health Checks
**Every service must expose health check endpoints:**
- **`/health` (Liveness)** — "Is the process alive?"
- Returns 200 if the process is running
- No dependency checks (database, cache, etc.)
- Used by orchestrators to decide whether to restart
- **`/ready` (Readiness)** — "Can the service accept traffic?"
- Checks all critical dependencies (database, cache, message queue)
- Returns 503 if any dependency is unavailable
- Used by load balancers to route traffic
**Rules:**
- Health checks must be fast (< 1 second)
- Health checks must not have side effects
- Separate liveness from readiness — they serve different purposes
### Metrics Instrumentation
**Instrument code using the RED method for services:**
- **Rate** — requests per second
- **Errors** — error count/rate
- **Duration** — request latency (use histograms, not averages)
**Instrument code using the USE method for resources:**
- **Utilization** — how much of the resource is used
- **Saturation** — how much work is queued
- **Errors** — error count for the resource
**Rules:**
- Use counters for things that only go up (requests, errors)
- Use gauges for things that go up and down (connections, queue depth)
- Use histograms for distributions (latency, response size)
- Label metrics consistently (service, method, status_code)
- Don't create high-cardinality labels (no user IDs as labels)
### Error Tracking Integration
- **Capture unhandled exceptions** with full stack traces
- **Include context** — user ID, request ID, correlation ID
- **Group errors** by root cause, not by instance
- **Set severity levels** based on user impact
### Graceful Degradation
- **Circuit breakers** for external dependencies — stop calling failing services
- **Fallbacks** for non-critical features — serve cached data, show reduced UI
- **Timeouts** on all external calls — never wait indefinitely
- **Retry with backoff** for transient failures — exponential backoff with jitter
### Implementation Notes
This rule is tool-agnostic. Whether the project uses Datadog, LGTM stack, Sentry,
New Relic, or CloudWatch — the code patterns (health checks, metrics, error tracking)
are the same. The specific client library belongs in project-level configuration.
### Monitoring Checklist
- [ ] Health check endpoints implemented (/health and /ready)?
- [ ] Liveness probe has no dependency checks?
- [ ] Readiness probe checks all critical dependencies?
- [ ] Key operations instrumented with RED/USE metrics?
- [ ] No high-cardinality metric labels?
- [ ] Unhandled exceptions captured with context?
- [ ] Circuit breakers on external dependencies?
- [ ] Timeouts on all external calls?
### Related Principles
- Logging and Observability Mandate @logging-and-observability-mandate.md
- Logging and Observability Principles @logging-and-observability-principles.md
- Error Handling Principles @error-handling-principles.md
- Resource and Memory Management Principles @resources-and-memory-management-principles.md
- Concurrency and Threading Principles @concurrency-and-threading-principles.md
@@ -0,0 +1,100 @@
---
trigger: model_decision
description: When working on performance optimization, profiling, benchmarking, or performance-critical code paths
---
## Performance Optimization Principles
### Measure Before Optimizing
**"Premature optimization is the root of all evil" - Donald Knuth**
**Process:**
1. **Measure:** Profile to find actual bottlenecks (don't guess)
2. **Identify:** Find the 20% of code consuming 80% of resources
3. **Optimize:** Improve that specific bottleneck
4. **Measure again:** Verify improvement with benchmarks
5. **Repeat:** Only if still not meeting performance goals
**Don't optimize:**
- Code that's "fast enough" for requirements
- Code that's rarely executed
- Without measurable performance problem
### Choose Appropriate Data Structures
**Selection matters:**
- Hash map: O(1) lookup, unordered
- Array/list: O(1) index access, O(n) search, ordered
- Binary tree: O(log n) operations, sorted order
- Set: O(1) membership testing, unique elements
**Wrong choice causes performance degradation:**
- Using array for lookups: O(n) when O(1) possible with hash map
- Using list for sorted data: O(n log n) sort vs O(log n) tree operations
### Avoid Premature Abstraction
**Abstraction has costs:**
- Runtime overhead (indirection, virtual dispatch, dynamic resolution)
- Cognitive overhead (understanding layers of abstraction)
- Maintenance overhead (changes ripple through abstractions)
**Start concrete, abstract when pattern emerges:**
- Write straightforward code first
- Identify duplication and common patterns
- Abstract only when there's clear benefit
- Don't add "for future flexibility" without evidence
### Optimization Techniques
**Caching:**
- Store expensive computation results
- Cache database queries, API responses, rendered templates
- Use appropriate cache invalidation strategy
- Set TTL (time-to-live) for cache entries
**Lazy Loading:**
- Compute only when needed
- Load data on-demand, not upfront
- Defer expensive operations until required
**Batching:**
- Process multiple items together
- Batch database queries (N queries → 1 query)
- Batch API requests where possible
**Async I/O:**
- Don't block on I/O operations
- Use async/await for concurrent I/O
- Process multiple I/O operations in parallel
**Connection Pooling:**
- Reuse expensive resources (database connections, HTTP connections)
- See Resource and Memory Management Principles @resources-and-memory-management-principles.md for pool sizing and configuration
### Performance Optimization Checklist
- [ ] Is there a measured performance problem (not a guess)?
- [ ] Have you profiled to find the actual bottleneck?
- [ ] Are appropriate data structures chosen for the access pattern?
- [ ] Are expensive operations cached with proper invalidation?
- [ ] Are batch operations used instead of N+1 queries?
- [ ] Are I/O operations non-blocking where appropriate?
- [ ] Have you measured improvement after optimization?
### Related Principles
- Resource and Memory Management Principles @resources-and-memory-management-principles.md
- Concurrency and Threading Mandate @concurrency-and-threading-mandate.md
- Concurrency and Threading Principles @concurrency-and-threading-principles.md
@@ -0,0 +1,117 @@
---
trigger: model_decision
description: When working on a Flutter or React Native mobile application, setting up mobile project structure
---
## Flutter/Mobile Layout
Use this structure for Flutter or React Native mobile applications. The vertical slice principle applies — features are self-contained modules.
```
apps/
mobile/ # Mobile application source code (Flutter)
lib/
core/ # Foundational concerns (the "Framework")
network/ # HTTP client setup, interceptors
api_client.dart # Dio/http client with base config
api_interceptor.dart # Auth token, logging interceptors
storage/ # Local storage abstractions
local_storage.dart # SharedPreferences / Hive wrapper
theme/ # App theming
app_theme.dart # ThemeData, color schemes
app_typography.dart # TextStyles, font families
router/ # Navigation / routing
app_router.dart # GoRouter config (@riverpod function)
app_router.g.dart # Generated provider code
constants/ # App-wide constants
features/ # Business Features (Vertical Slices)
task/ # Task management feature
# --- Presentation ---
screens/
task_list_screen.dart # Full screen (route target, ConsumerWidget)
task_detail_screen.dart
widgets/ # Feature-specific widgets
task_card.dart
task_form.dart
# --- State Management (Riverpod 3) ---
state/
task_notifier.dart # @riverpod class (Notifier/AsyncNotifier)
task_notifier.g.dart # Generated provider code (build_runner)
task_state.dart # State classes (generated via freezed)
# --- Domain (Business Logic) ---
models/
task.dart # Domain model (freezed)
task.g.dart # Generated code (json_serializable)
task.freezed.dart # Generated code (freezed)
logic/
task_logic.dart # Pure business rules
# --- Data (I/O Abstraction) ---
repository/
task_repository.dart # Abstract repository interface
task_repository_impl.dart # Implementation (API + cache)
task_repository_mock.dart # Mock for testing
api/
task_api.dart # REST API calls (Dio)
auth/ # Authentication feature
...
settings/ # Settings feature
...
shared/ # Shared across features
widgets/ # Reusable UI components (NO domain logic)
app_button.dart
app_text_field.dart
loading_indicator.dart
utils/ # Pure utility functions
date_formatter.dart
validators.dart
models/ # Shared data models
api_response.dart
pagination.dart
test/ # Test directory (mirrors lib/ layout)
features/
task/
state/
task_notifier_test.dart # Unit tests for state logic
logic/
task_logic_test.dart # Unit tests for pure business rules
widgets/
task_card_test.dart # Widget tests
api/
task_api_test.dart # API integration tests
integration_test/ # Integration / E2E tests
task_flow_test.dart
pubspec.yaml # Dependencies
analysis_options.yaml # Lint rules (includes riverpod_lint)
```
**Key differences from web frontend:**
- `screens/` replaces `views/` — mobile uses screen-based navigation
- `widgets/` replaces `components/` — Flutter's terminology
- `state/` replaces `store/` — Riverpod 3 `@riverpod` class (Notifier/AsyncNotifier) instead of Pinia/Redux
- `*.g.dart` files in `state/` — generated provider code from `riverpod_generator` + `build_runner`
- `repository/` replaces `api/` — mobile often caches data locally
- No separate `di/` directory — Riverpod 3 uses `ProviderScope` at the app root for dependency injection; no manual service locator needed
**Codegen artifacts (gitignore-able):**
- `*.g.dart` — generated by `riverpod_generator` and `json_serializable`
- `*.freezed.dart` — generated by `freezed`
- Run `dart run build_runner build --delete-conflicting-outputs` after changes
**Test location:**
Tests live in the `test/` directory, mirroring the `lib/` layout. This is Flutter's default convention — `flutter test` discovers tests here automatically with no extra configuration.
- **Naming:** `*_test.dart` (unit), `*_integration_test.dart` (integration)
- **Discovery:** `flutter test` (runs all tests in `test/`)
- **Structure:** Mirror the feature directory structure from `lib/`
### Related Principles
- Project Structure @project-structure.md (core philosophy)
- Flutter Idioms and Patterns @flutter-idioms-and-patterns.md (Riverpod 3 patterns, widget idioms)
@@ -0,0 +1,59 @@
---
trigger: model_decision
description: When working on a Go backend project, setting up Go project structure, or organizing Go services and APIs
---
## Go Backend Layout
Use this structure for Go backend applications. The vertical slice principle applies — features are packages, not layers.
```
apps/
backend/ # Backend application source code
cmd/
api/
main.go # Entry point: Wires dependencies, router, starts server
internal/ # Private application code
platform/ # Foundational technical concerns (The "Framework")
database/ # DB connection logic
server/ # HTTP server setup (Router, Middleware)
logger/ # Structured logging setup
features/ # Business Features (Vertical Slices)
task/ # Task management
# --- Interface Definition ---
service.go # The public API of this feature (Service struct)
# --- Delivery (HTTP) ---
handler.go # HTTP Handlers
handler_test.go # Component tests (httptest + mock service)
# --- Domain (Business Logic) ---
logic.go # Core business logic methods
logic_test.go # Unit tests (Pure functions + mock storage)
models.go # Domain structs (Task, NewTaskRequest)
errors.go # Feature-specific errors
# --- Storage (Data Access) ---
storage.go # Storage Interface definition
storage_pg.go # Postgres implementation
postgres_integration_test.go # Integration tests (Real DB/Testcontainers)
storage_mock.go # Mock implementation
...
order/ # Order management
handler.go
logic.go
storage.go
...
```
**Key Go conventions:**
- `cmd/` for entry points — each subdirectory is a separate binary
- `internal/` for private packages — enforced by Go compiler (cannot be imported externally)
- `platform/` for foundational concerns (database, server, logger)
- `features/` for vertical business slices — each feature is self-contained
- Tests live alongside the code they test (`_test.go` suffix)
- `go.mod` at the root — single module for the entire application
### Related Principles
- Project Structure @project-structure.md (core philosophy)
- Go Idioms and Patterns @go-idioms-and-patterns.md (coding idioms, error handling, naming)
@@ -0,0 +1,144 @@
---
trigger: model_decision
description: When working on a Python backend project, setting up Python project structure, or organizing Python services and APIs
---
## Python Backend Layout
Use this structure for Python backend applications. The vertical slice principle applies — features are packages, not technical layers.
```
apps/
backend/ # Backend application source code
pyproject.toml # Project metadata, dependencies, tool configs
src/
yourapp/ # Importable package (src-layout, preferred)
main.py # Entry point: creates app, wires dependencies, starts server
platform/ # Foundational technical concerns (the "Framework")
database.py # DB engine and session factory
server.py # ASGI app setup (FastAPI/Starlette router, middleware)
logger.py # structlog / logging configuration
config.py # Settings (pydantic-settings BaseSettings)
features/ # Business Features (Vertical Slices)
task/ # Task management
__init__.py
# --- Public API ---
service.py # TaskService class (public API of this feature)
# --- Delivery (HTTP) ---
router.py # FastAPI APIRouter with HTTP endpoints
schemas.py # Pydantic request/response models (API boundary only)
test_router.py # Component tests (TestClient + mock service)
# --- Domain (Business Logic) ---
logic.py # Pure domain functions (no I/O)
models.py # Domain dataclasses (Task, Priority, etc.)
errors.py # Feature-specific exceptions
test_logic.py # Unit tests (pure functions — no mocks needed)
# --- Storage (Data Access) ---
storage.py # TaskStorage Protocol (interface)
storage_pg.py # PostgreSQL implementation (asyncpg / SQLAlchemy)
storage_mock.py # InMemoryTaskStorage (test implementation)
test_storage_pg.py # Integration tests (real DB via testcontainers)
order/ # Order management
service.py
router.py
schemas.py
logic.py
models.py
storage.py
storage_pg.py
storage_mock.py
...
tests/ # Optional: top-level E2E tests (cross-feature boundaries)
e2e/
test_create_task_api.e2e.py
```
**Key Python conventions:**
- **`src/` layout** is strongly preferred — prevents accidental imports of the development tree and matches packaging best practices (PEP 517 / `pypa/build`)
- **`pyproject.toml`** is the single configuration file for `ruff`, `mypy`, `pytest`, `bandit`, and packaging metadata — do not create `setup.cfg`, `.flake8`, or `mypy.ini` files
- **Feature packages** use `__init__.py` — keep it empty or use it solely to re-export the feature's public API
- **`platform/`** holds technical infrastructure that features depend on (database sessions, configuration, logging); features never import each other's `platform/` code directly
- **Tests co-locate** with the code they test (`test_*.py` in the same directory) except for E2E tests which go in `tests/e2e/`
- **`storage_mock.py`** is a production-quality in-memory implementation, not a `unittest.Mock` — it is the recommended test double for unit and integration tests
### Dependency Wiring (main.py)
```python
# src/yourapp/main.py
from yourapp.platform.database import create_engine
from yourapp.platform.server import create_app
from yourapp.features.task.storage_pg import PostgresTaskStorage
from yourapp.features.task.service import TaskService
from yourapp.features.task.router import build_router as build_task_router
def create_application() -> FastAPI:
engine = create_engine()
task_storage = PostgresTaskStorage(engine=engine)
task_service = TaskService(storage=task_storage)
app = create_app()
app.include_router(build_task_router(service=task_service))
return app
app = create_application()
```
### Configuration (platform/config.py)
```python
# src/yourapp/platform/config.py
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
database_url: str
secret_key: str
debug: bool = False
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
def get_settings() -> Settings:
return Settings()
```
### pyproject.toml Baseline
```toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "yourapp"
requires-python = ">=3.11"
[tool.hatch.build.targets.wheel]
packages = ["src/yourapp"]
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "S", "B", "ANN"]
[tool.mypy]
strict = true
python_version = "3.11"
[tool.pytest.ini_options]
testpaths = ["src"]
asyncio_mode = "auto"
[tool.bandit]
skips = ["B101"] # assert statements allowed in test files
```
### Related Principles
- Project Structure @project-structure.md (core philosophy)
- Python Idioms and Patterns @python-idioms-and-patterns.md (coding idioms, error handling, naming)
@@ -0,0 +1,216 @@
---
trigger: model_decision
description: When working on a Rust or Cargo project, setting up Rust project structure, or organizing Rust crates and workspaces
---
## Rust/Cargo Layout
Use these structures for Rust projects. The vertical slice principle applies — features are modules, not layers.
### Single Binary Crate
For CLI tools, standalone servers, and single-purpose applications.
```
project-root/
Cargo.toml # Package manifest + dependencies
build.rs # Build script (optional — for FFI, codegen)
src/
main.rs # Entry point: CLI parsing, server startup, wires dependencies
lib.rs # Library root — re-exports public modules (enables integration testing)
# --- Foundational Concerns (The "Platform") ---
config.rs # Configuration loading (env vars, config files)
error.rs # Application-wide error types (thiserror enum)
telemetry.rs # Tracing/logging setup (tracing, tracing-subscriber)
# --- Business Features (Vertical Slices) ---
features/
mod.rs # Re-exports feature modules
task/
mod.rs # Public API of this feature — re-exports types + service
service.rs # Feature service (business logic orchestration)
logic.rs # Pure business rules (no I/O, easily testable)
models.rs # Domain structs (Task, NewTaskRequest)
error.rs # Feature-specific error types
repository.rs # Storage trait definition
postgres.rs # Postgres implementation of repository trait
mock.rs # Mock implementation for testing
auth/
mod.rs
service.rs
logic.rs
models.rs
...
# --- Delivery Layer (HTTP/gRPC) ---
handlers/
mod.rs
task_handler.rs # HTTP handlers for task feature (axum/actix extractors)
auth_handler.rs
router.rs # Route definitions, middleware stack
tests/ # Integration tests (compiled as separate crate, access only pub API)
common/
mod.rs # Shared test fixtures, helpers, test database setup
task_api_test.rs # Full API integration tests
auth_flow_test.rs
benches/ # Benchmarks (criterion)
task_benchmark.rs
```
**Key Rust conventions:**
- `src/lib.rs` + `src/main.rs` — enables integration testing against `lib.rs` public API
- `mod.rs` re-exports — each feature's `mod.rs` is its public boundary
- `error.rs` per feature — typed errors with `thiserror`, composed at app level with `#[from]`
- `tests/` directory — integration tests see only the public API, enforcing encapsulation
- No `controllers/` or `services/` at the top level — features are the top-level organization
---
### Library Crate
For publishable libraries, SDKs, and reusable components.
```
project-root/
Cargo.toml # Package manifest (lib target)
src/
lib.rs # Crate root — public API surface, module re-exports
parser.rs # Public module
ast.rs # Public module
error.rs # Public error types (thiserror)
internal/ # Private implementation details
mod.rs
optimizer.rs
cache.rs
examples/ # Runnable examples (cargo run --example)
basic_usage.rs
advanced_config.rs
tests/ # Integration tests
parsing_test.rs
benches/ # Benchmarks
parser_benchmark.rs
```
---
### Cargo Workspace (Multi-Crate)
For complex projects with multiple internal crates. This is the recommended structure for projects where distinct subsystems benefit from separate compilation units and explicit dependency boundaries.
```
project-root/
Cargo.toml # [workspace] manifest — lists all members
Cargo.lock # Locked dependency versions (committed for binaries)
crates/
myapp/ # Main binary crate (entry point, wiring)
Cargo.toml # Depends on other workspace crates
src/
main.rs # CLI parsing, server setup
lib.rs
config.rs
error.rs
api/ # API / protocol handling
mod.rs
routes.rs # Route definitions
handlers.rs # Request handlers
myapp-parser/ # Domain-specific engine crate
Cargo.toml
build.rs # Build script (optional — for FFI, codegen)
src/
lib.rs
parser.rs # Core parsing logic
transform.rs # Data transformations
cache.rs # Caching layer
tests/ # Per-crate integration tests (public API only)
parser_integration.rs
myapp-client/ # External service client crate
Cargo.toml
src/
lib.rs
client.rs # Client implementation
lifecycle.rs # Connection management
retry.rs # Retry and resilience logic
myapp-search/ # Search / query engine crate
Cargo.toml
src/
lib.rs
search.rs # Search implementation
filter.rs # Result filtering
tests/ # Per-crate integration tests
search_integration.rs
myapp-common/ # Shared types and utilities
Cargo.toml
src/
lib.rs
types.rs # Shared domain types
error.rs # Common error types
tests/ # Per-crate integration tests
common_integration.rs
tests/ # Workspace-level integration tests
integration/
full_pipeline_test.rs # End-to-end tests across crates
config/ # Default configuration
myapp.config.default.json
```
**Key differences from Go/Node layouts:**
- `crates/` replaces `apps/` — Cargo workspace with explicit inter-crate dependencies in `Cargo.toml`
- Each crate has its own `Cargo.toml` — dependencies are scoped, compile separately, enforces boundaries
- `build.rs` — Rust-specific build script for FFI compilation (C grammars for tree-sitter)
- `Cargo.lock` committed — standard practice for binary projects (not for library crates)
- No `node_modules/`, `vendor/` — dependencies are managed by Cargo globally in `~/.cargo/`
- Feature flags in `Cargo.toml` — use `[features]` for optional functionality instead of build-time env vars
### Test Organization in Rust
Rust has a compiler-enforced two-tier test system that differs fundamentally from Go/TS:
**Unit Tests — Inline (Not Separate Files)**
- Convention: `#[cfg(test)] mod tests` block **at the bottom of each `.rs` file**
- Tests are compiled conditionally — stripped from production builds
- Can access **private** functions via `use super::*`
- This is NOT a shortcut — it is the official, idiomatic Rust convention
- Do NOT create separate `*_test.rs` files for unit tests
**Integration Tests — `tests/` Directory**
- Location: `tests/` directory **at each crate's root** (next to `src/`)
- Each `.rs` file in `tests/` is compiled as its own **separate crate**
- Can only access the crate's **public API** (`use my_crate::...`)
- No `#[cfg(test)]` needed — Cargo treats `tests/` as test-only automatically
- Shared test helpers go in `tests/common/mod.rs` (not `tests/common.rs`)
**Workspace-level tests:**
- For cross-crate integration / E2E tests, place in `tests/` at the workspace root
- Or create a dedicated test crate in the workspace (e.g. `tests-integration/` — tokio pattern)
**Example per-crate layout with tests:**
```
crates/pathfinder-search/
Cargo.toml
src/
lib.rs # Contains #[cfg(test)] mod tests { ... } at bottom
search.rs # Contains #[cfg(test)] mod tests { ... } at bottom
filter.rs # Contains #[cfg(test)] mod tests { ... } at bottom
mock.rs # Mock implementations
tests/ # Integration tests (separate crate, public API only)
search_integration.rs # Tests search through public lib.rs API
common/
mod.rs # Shared test fixtures and helpers
```
### Related Principles
- Project Structure @project-structure.md (core philosophy)
- Rust Idioms and Patterns @rust-idioms-and-patterns.md
@@ -0,0 +1,76 @@
---
trigger: model_decision
description: When working on a web frontend project using Vue, React, Svelte, or similar frameworks, setting up frontend project structure
---
## Vue/React Frontend Layout
Use this structure for web frontend applications. The vertical slice principle applies — features are self-contained modules, not scattered across global folders.
```
apps/
frontend/ # Frontend application source code
src/
assets/ # Fonts, Images
features/ # Business features organized as vertical slices. Each feature is SELF-CONTAINED.
task/ # Task management
components/ # Task Feature-specific components go HERE, DON'T Put feature components in top-level folders
TaskForm.vue
TaskListItem.vue
TaskFilters.vue
TaskInput.vue
TaskInput.spec.ts # Component unit tests
store/
task.store.ts # Pinia store
task.store.spec.ts # Store unit tests
api/
task.api.ts # interface TaskAPI
task.api.backend.ts # Production implementation
task.api.mock.ts # Test implementation
services/
task.service.ts # Business logic
task.service.spec.ts # Logic unit tests
types/ # TS Interfaces for tasks (e.g. CreateTaskDTO interfaces)
composables/ # Task Feature-specific hooks (e.g. useTaskFilters.ts)
index.ts # Public exports. Export ONLY what's needed by `views/`
order/
composables/ # Global reactive logic (useAuth, useTheme)
components/ # Shared Component (Buttons, Inputs) - Dumb UI, No Domain Logic. DON'T Put feature components HERE
ui/ # UI Components (Atoms & Molecules) Pure, reusable UI primitives. NO domain logic, NO feature knowledge.
BaseButton.vue
BaseButton.spec.ts # Unit tests for button states
types.ts # Shared UI types/interfaces
index.ts # Barrel export for easy imports
layout/ # Layout Components (Organisms) Composite UI structures that combine multiple UI components. Still reusable, but more complex.
AppHeader.vue # Application header with nav, logo, user menu
AppSidebar.vue # Sidebar navigation structure
ErrorBoundary.vue # Error display wrapper
EmptyState.vue # Empty list placeholder
layouts/ # App shells (Sidebar, Navbar wrappers)
MainLayout.vue # Contains Navbar, Sidebar, Footer
AuthLayout.vue # Minimal layout for Login/Register
views/ # Route entry points (The "Glue")
HomeView.vue # Imports from features/analytics
TaskView.vue # Imports from features/task
utils/ # Pure, stateless helper functions. No domain knowledge, no Vue reactivity, (e.g. date-fns wrappers, math).
router/ # Route definitions
plugins/ # Library configs (Axios, I18n)
App.vue # Root component (hosts <router-view>)
main.ts # Entry point (bootstraps plugins & mounts app)
...
```
**Key frontend conventions:**
- `features/` for vertical slices — each feature exports only what `views/` needs via `index.ts`
- `components/ui/` for **shared** dumb UI (buttons, inputs) — NO domain logic, NO feature knowledge
- `components/layout/` for composite UI structures (headers, sidebars)
- `views/` are route entry points — they compose features, not implement them
- Feature components live **inside** the feature, NOT in top-level `components/`
- `composables/` at root for global reactive logic; feature-specific hooks inside the feature
> This structure applies equally to React (.tsx), Vue (.vue), and Svelte (.svelte). Replace component file extensions and state management (Redux/Zustand for React, Pinia for Vue) as needed.
### Related Principles
- Project Structure @project-structure.md (core philosophy)
- Vue Idioms and Patterns @vue-idioms-and-patterns.md (Composition API, Pinia, composables)
- TypeScript Idioms and Patterns @typescript-idioms-and-patterns.md (type system, async patterns)
+83
View File
@@ -0,0 +1,83 @@
---
trigger: always_on
---
> **This file is the SINGLE SOURCE OF TRUTH for project organization.**
> All other rules and workflows that reference paths should defer to this file.
> To adapt the setup for a different project type, edit this file only.
## Project Structure
**Project Structure Philosophy:**
- **Organize by FEATURE, not by technical layer**
- Each feature is a vertical slice
- Enables modular growth, clear boundaries, and independent deployability
**Universal Rule: Context → Feature → Layer**
**1. Level 1: Repository Scope:** Root contains `apps/` grouping distinct applications (e.g., `apps/backend`, `apps/frontend`, `apps/mobile`).
**2. Level 2: Feature Organization**
- **Rule:** Divide application into vertical business slices (e.g., `user/`, `order/`, `payment/`).
- **Anti-Pattern:** Do NOT organize by technical layer (e.g., `controllers/`, `models/`, `services/`) at the top level.
### Language-Specific Layouts
Each layout follows the universal philosophy above. Load the relevant layout when working with a specific language or framework:
| Layout | File | When to Use |
| ------------------ | ------------------------------------ | ------------------------------------ |
| Go Backend | @project-structure-go-backend.md | Go services, APIs, CLI tools |
| Vue/React Frontend | @project-structure-vue-frontend.md | Web frontends (Vue, React, Svelte) |
| Flutter/Mobile | @project-structure-flutter-mobile.md | Mobile apps (Flutter, React Native) |
| Rust/Cargo | @project-structure-rust-cargo.md | Rust binaries, libraries, workspaces |
> This Feature/Domain/UI/API structure is framework-agnostic. It applies equally to any language or framework. The layout files provide language-specific conventions (file naming, module systems, test locations) while preserving the vertical slice architecture.
### Adapting for Different Project Types
| Project Type | What to Change |
| ----------------------- | ------------------------------------------------------------------------------------------------- |
| **Monorepo** (default) | Use as-is — `apps/backend/`, `apps/frontend/`, `apps/mobile/` |
| **Single backend** | Flatten to root: `cmd/`, `internal/` (Go) or `src/` (Rust) at project root |
| **Single frontend** | Flatten to root: `src/` at project root (no `apps/` wrapper) |
| **Single mobile** | Flatten to root: `lib/` at project root (no `apps/` wrapper) |
| **Single Rust binary** | Flatten to root: `src/`, `tests/`, `benches/` at project root with `Cargo.toml` |
| **Rust library** | Flatten to root: `src/`, `examples/`, `tests/` at project root with `Cargo.toml` (lib target) |
| **Rust workspace** | `crates/` at root with workspace `Cargo.toml` — each crate follows single-crate layout internally |
| **Microservices** | One directory per service under `apps/` (each with own `go.mod`/`Cargo.toml`/`Dockerfile`) |
| **Full-stack + mobile** | Use all relevant layout files under `apps/` |
**Single-app projects** don't need the `apps/` directory — put the language-specific root directories directly at the project root. The internal structure (features, platform, etc.) stays the same.
**Multiple entry points (CLI, workers, etc.):**
Go:
```
cmd/
api/main.go # HTTP server entry point
cli/main.go # CLI tool entry point
worker/main.go # Background worker entry point
```
Rust:
```
src/
bin/
api.rs # HTTP server entry point
cli.rs # CLI tool entry point
worker.rs # Background worker entry point
lib.rs # Shared library code
```
**Microservices notes:**
- Each service is its own directory under `apps/` with its own `go.mod`/`Cargo.toml` and `Dockerfile`
- Each service follows the same layout internally (see language-specific files)
- Add `shared/` at root for cross-service contracts (protobuf, shared types) — keep this minimal
- Services communicate via API calls or message queues, never direct imports
### Related Principles
- Code Organization Principles @code-organization-principles.md
- Architectural Patterns — Testability-First Design @architectural-pattern.md
+456
View File
@@ -0,0 +1,456 @@
---
trigger: model_decision
description: When writing Python code, reviewing Python idioms, or working on any Python project (backend, CLI, or data pipeline)
---
## Python Idioms and Patterns
### Core Philosophy
Python rewards explicitness and readability over cleverness. Follow the **Zen of Python** (`import this`) — beautiful code is not a luxury, it's a professional necessity. If it reads like plain English, it's probably idiomatic Python.
> **Scope:** This file covers Python-specific *coding idioms*. For file layout, see `project-structure-python-backend.md`. For test naming conventions, see `testing-strategy.md`. For logging library choice, see `logging-and-observability-principles.md`.
---
### Type Hints — Non-Negotiable
**Always annotate function signatures and public APIs.** Use `from __future__ import annotations` for forward references.
```python
# ✅ Fully annotated — self-documenting and mypy-verifiable
from __future__ import annotations
from collections.abc import Sequence
def calculate_discount(items: Sequence[Item], coupon: Coupon) -> float: ...
# ❌ Untyped — opaque to both mypy and the next developer
def calculate_discount(items, coupon): ...
```
1. **Use `X | None` over `Optional[X]`** (Python 3.10+)
```python
# ✅ Modern union syntax
def find_user(user_id: str) -> User | None: ...
# ❌ Verbose legacy form (still valid, but prefer the above)
from typing import Optional
def find_user(user_id: str) -> Optional[User]: ...
```
2. **Use `TypeAlias` and `TypeVar` for reusable generics**
```python
from typing import TypeVar, TypeAlias
T = TypeVar("T")
UserId: TypeAlias = str
```
3. **Use `Protocol` for structural interfaces instead of ABCs when duck-typing is sufficient**
```python
from typing import Protocol
class TaskStorage(Protocol):
def get_by_id(self, task_id: str) -> Task: ...
def save(self, task: Task) -> None: ...
```
4. **`TypedDict` for structured dicts crossing system boundaries (JSON, configs)**
```python
from typing import TypedDict
class CreateTaskRequest(TypedDict):
title: str
priority: Literal["low", "medium", "high"]
```
---
### Error Handling
> For general error handling principles, see `error-handling-principles.md`. This section covers Python-specific idioms.
1. **Prefer specific exception types over broad `except Exception`**
```python
# ✅ Precise — catches exactly what you expect
try:
task = storage.get_by_id(task_id)
except TaskNotFoundError:
raise HTTPException(status_code=404, detail="Task not found")
# ❌ Broad — may swallow programming errors
try:
task = storage.get_by_id(task_id)
except Exception:
raise HTTPException(status_code=500, detail="Error")
```
2. **Define domain-specific exception hierarchies**
```python
class FathError(Exception):
"""Base exception for all domain errors."""
class NotFoundError(FathError):
def __init__(self, resource: str, resource_id: str) -> None:
self.resource = resource
self.resource_id = resource_id
super().__init__(f"{resource} '{resource_id}' not found")
class ValidationError(FathError):
def __init__(self, field: str, message: str) -> None:
self.field = field
self.message = message
super().__init__(f"Validation failed on '{field}': {message}")
```
3. **Never silence exceptions** — if an exception is caught and not re-raised, log it explicitly
```python
# ❌ Silent swallow
try:
notify_user(user_id)
except Exception:
pass
# ✅ Explicit intent + observability
try:
notify_user(user_id)
except NotificationError:
logger.warning("notification_failed", user_id=user_id, exc_info=True)
```
4. **Use `contextlib.suppress` only for truly expected, inconsequential exceptions**
```python
from contextlib import suppress
with suppress(FileNotFoundError):
cache_path.unlink() # OK — cleanup, not business logic
```
---
### Dataclasses and Pydantic
1. **Use `dataclasses` for internal domain models** (no I/O, no validation)
```python
from dataclasses import dataclass, field
@dataclass(frozen=True) # frozen = immutable value object
class Task:
id: str
title: str
priority: str
tags: tuple[str, ...] = field(default_factory=tuple)
```
2. **Use Pydantic `BaseModel` for data crossing system boundaries** (API requests/responses, config)
```python
from pydantic import BaseModel, Field
class CreateTaskRequest(BaseModel):
title: str = Field(min_length=1, max_length=200)
priority: Literal["low", "medium", "high"] = "medium"
due_date: datetime | None = None
model_config = ConfigDict(frozen=True) # Pydantic v2
```
3. **Keep domain models separate from API schemas** — never use a Pydantic model as a domain entity
```
models.py → dataclasses (pure domain)
schemas.py → Pydantic models (API boundary)
```
---
### Interfaces and Dependency Injection
Python uses Protocols and constructor injection to achieve the same testability goal as Go interfaces.
1. **Define the Protocol where it is *used*, not where it is *implemented***
```python
# task/storage.py ← defined in the consumer feature
from typing import Protocol
class TaskStorage(Protocol):
def get_by_id(self, task_id: str) -> Task: ...
def save(self, task: Task) -> None: ...
def delete(self, task_id: str) -> None: ...
```
2. **Inject dependencies through `__init__`** — never instantiate concrete dependencies inside a class
```python
# ✅ Testable — storage is injected
class TaskService:
def __init__(self, storage: TaskStorage) -> None:
self._storage = storage
# ❌ Not testable — concrete dependency hardwired
class TaskService:
def __init__(self) -> None:
self._storage = PostgresTaskStorage()
```
3. **Wire dependencies in the entry point** (e.g., `main.py`, `app.py`, or DI container)
```python
# app/main.py
storage = PostgresTaskStorage(db=database)
service = TaskService(storage=storage)
router.include_router(build_task_router(service))
```
---
### Async / Await
> For general async principles (when to add concurrency), see `concurrency-and-threading-mandate.md`. This section covers Python-specific async idioms.
1. **Choose one async paradigm and stay consistent** — do not mix `asyncio.run` entry points
```python
# ✅ Fully async service layer
async def get_task(self, task_id: str) -> Task:
return await self._storage.get_by_id(task_id)
```
2. **Never call blocking I/O directly in an async function**
```python
# ❌ Blocks the event loop
async def load_file(path: str) -> str:
return open(path).read()
# ✅ Use async I/O or run in executor
import asyncio, aiofiles
async def load_file(path: str) -> str:
async with aiofiles.open(path) as f:
return await f.read()
```
3. **Use `asyncio.gather` for concurrent independent operations**
```python
# ✅ Concurrent fan-out
user, tasks = await asyncio.gather(
get_user(user_id),
get_tasks(user_id),
)
```
4. **Use `asyncio.TaskGroup` (Python 3.11+) for structured concurrency with cancellation safety**
```python
async with asyncio.TaskGroup() as tg:
user_task = tg.create_task(get_user(user_id))
tasks_task = tg.create_task(get_tasks(user_id))
user = user_task.result()
tasks = tasks_task.result()
```
---
### Naming Conventions
Follow **PEP 8** rigorously. No exceptions.
| Construct | Convention | Example |
| ----------------------- | ------------------ | ---------------------------- |
| Module / Package | `snake_case` | `task_service.py` |
| Class | `PascalCase` | `TaskService` |
| Function / Method | `snake_case` | `get_by_id` |
| Private method/attr | `_snake_case` | `_validate_title` |
| Constant | `UPPER_SNAKE_CASE` | `MAX_TITLE_LENGTH = 200` |
| Type alias | `PascalCase` | `UserId = str` |
| Protocol / Interface | `PascalCase` | `TaskStorage` |
1. **Never use single-letter names outside list comprehensions or math** — names must be descriptive
2. **Avoid `data`, `info`, `obj`, `result` as standalone names** — describe the *domain concept*
3. **Boolean variables and functions should read as yes/no questions**
```python
# ✅
is_active: bool
has_permission: bool
def can_edit(user: User, task: Task) -> bool: ...
# ❌
active: bool
permission: bool
```
---
### Idiomatic Patterns
1. **Context managers for resource cleanup** — always prefer `with` over manual `close()`
```python
# ✅
async with database.transaction() as tx:
await tx.execute(query)
# ❌
tx = database.begin()
tx.execute(query)
tx.commit() # easily forgotten on exception
```
2. **Generator expressions over list comprehensions for lazy evaluation**
```python
# ✅ Lazy — does not materialise the entire list
active_ids = (task.id for task in tasks if task.is_active)
# Use list comprehension only when you need the full list
active_tasks = [task for task in tasks if task.is_active]
```
3. **`dataclasses.replace()` for immutable updates** (preferred over mutating frozen dataclasses)
```python
from dataclasses import replace
updated_task = replace(task, title="New Title")
```
4. **`functools.cache` / `functools.lru_cache` for pure function memoization**
```python
from functools import cache
@cache
def get_config() -> AppConfig: # called once; result reused
return _load_config_from_env()
```
5. **`__slots__` on hot-path, frequently instantiated classes**
```python
@dataclass
class Vector:
__slots__ = ("x", "y")
x: float
y: float
```
6. **`enum.Enum` (not raw strings) for domain-level constants**
```python
from enum import StrEnum # Python 3.11+
class Priority(StrEnum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
```
---
### Testing
> Test file naming and pyramid proportions are defined in `testing-strategy.md`. This section covers Python-specific tooling only.
1. **Use `pytest` as the sole test runner** — never mix with `unittest.TestCase` classes
```python
# ✅ Idiomatic pytest
def test_calculate_discount_returns_zero_for_no_items() -> None:
result = calculate_discount(items=[], coupon=Coupon(code="SAVE10"))
assert result == 0.0
```
2. **Parametrize test cases with `@pytest.mark.parametrize`**
```python
@pytest.mark.parametrize("priority,expected_score", [
("low", 1),
("medium", 5),
("high", 10),
])
def test_priority_score(priority: str, expected_score: int) -> None:
assert priority_score(priority) == expected_score
```
3. **Use `pytest-mock` (`mocker` fixture) for mocking — not `unittest.mock` directly**
```python
def test_task_service_creates_task(mocker: MockerFixture) -> None:
mock_storage = mocker.create_autospec(TaskStorage, instance=True)
service = TaskService(storage=mock_storage)
service.create(title="Test", priority="high")
mock_storage.save.assert_called_once()
```
4. **Use a typed mock factory for Protocol-based interfaces**
```python
# storage_mock.py ← co-locate with storage.py
class InMemoryTaskStorage:
"""In-memory TaskStorage implementation for unit tests."""
def __init__(self) -> None:
self._store: dict[str, Task] = {}
def get_by_id(self, task_id: str) -> Task:
if task_id not in self._store:
raise NotFoundError("Task", task_id)
return self._store[task_id]
def save(self, task: Task) -> None:
self._store[task.id] = task
def delete(self, task_id: str) -> None:
self._store.pop(task_id, None)
```
5. **Use `pytest-asyncio` for async tests** — mark the whole module or use `asyncio_mode = "auto"` in `pyproject.toml`
```python
import pytest
@pytest.mark.asyncio
async def test_async_create_task() -> None:
service = TaskService(storage=InMemoryTaskStorage())
task = await service.create(title="Async Task", priority="low")
assert task.title == "Async Task"
```
6. **Fixtures for reusable setup** — never repeat identical `Arrange` blocks across tests
```python
@pytest.fixture
def task_service() -> TaskService:
return TaskService(storage=InMemoryTaskStorage())
```
---
### Formatting and Static Analysis
All of the following **must pass with zero warnings/errors** before any commit. See `code-completion-mandate.md` for the full checklist.
| Tool | Purpose | Command |
| ------------ | --------------------------------- | ---------------------------------- |
| `ruff format`| Canonical formatting (fast) | `ruff format .` |
| `ruff check` | Lint (replaces flake8, isort, ...) | `ruff check . --fix` |
| `mypy` | Static type checking | `mypy src/ --strict` |
| `bandit` | Security scanning | `bandit -r src/ -c pyproject.toml` |
| `pip-audit` | Dependency CVE scanning | `pip-audit` |
Configure all tools in `pyproject.toml` — never use per-file pragma comments to disable checks without a `# NOQA:` reason comment.
```toml
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "S", "B", "ANN"]
ignore = []
[tool.mypy]
strict = true
python_version = "3.11"
[tool.pytest.ini_options]
asyncio_mode = "auto"
```
> **Logging:** Never use `print()` in production code — it produces unstructured output. Use the standard `logging` module or `structlog` for structured JSON logs. See `logging-and-observability-principles.md` for the required patterns.
---
### Related Principles
- Code Idioms and Conventions @code-idioms-and-conventions.md
- Project Structure — Python Backend @project-structure-python-backend.md
- Testing Strategy @testing-strategy.md
- Error Handling Principles @error-handling-principles.md
- Concurrency and Threading Mandate @concurrency-and-threading-mandate.md
- Logging and Observability Principles @logging-and-observability-principles.md
- Security Principles @security-principles.md
- Dependency Management Principles @dependency-management-principles.md
@@ -0,0 +1,129 @@
---
trigger: model_decision
description: When working with resources requiring cleanup (files, database connections, network sockets, locks) or implementing resource pools
---
## Resource and Memory Management Principles
### Universal Resource Management Rules
**1. Always Clean Up Resources**
**Resources requiring cleanup:**
- Files, network connections, database connections
- Locks, semaphores, mutexes
- Memory allocations (in manual-memory languages)
- OS handles, GPU resources
**Clean up in ALL paths:**
- Success path: Normal completion
- Error path: Exception thrown, error returned
- Early return path: Guard clauses, validation failures
**Use language-appropriate patterns:**
- Go: defer statements
- Rust: Drop trait (RAII)
- Python: context managers (with statement)
- TypeScript: try/finally
- Java: try-with-resources
**2. Timeout All I/O Operations**
**Why timeout:**
- Network requests can hang indefinitely
- Prevents resource exhaustion (connections, threads)
- Provides predictable failure behavior
**Timeout recommendations:**
- Network requests: 30s default, shorter (5-10s) for interactive
- Database queries: 10s default, configure per query complexity
- File operations: Usually fast, but timeout on network filesystems
- Message queue operations: Configurable, avoid indefinite blocking
**3. Pool Expensive Resources**
**Resources to pool:**
- Database connections: Pool size 5-20 per app instance
- HTTP connections: Reuse with keep-alive
- Thread pools: Size based on CPU count (CPU-bound) or I/O wait (I/O-bound)
**Benefits:**
- Reduces latency (no connection setup overhead)
- Limits resource consumption (cap on max connections)
- Improves throughput (reuse vs create new)
**Connection Pool Best Practices:**
- Minimum connections: 5 (ensures pool is warm)
- Maximum connections: 20-50 (prevents overwhelming database)
- Idle timeout: Close connections idle >5-10 minutes
- Validation: Test connections before use (avoid broken connections)
- Monitoring: Track utilization, wait times, timeout rates
**4. Avoid Resource Leaks**
**What is a leak:**
- Acquire resource (open file, allocate memory, get connection)
- Never release it (forget to close, exception prevents cleanup)
- Eventually exhaust system resources (OOM, max connections, file descriptors)
**Detection:**
- Monitor open file descriptors, connection counts, memory usage over time
- Run long-duration tests, verify resource counts stay stable
- Use leak detection tools (valgrind, ASan, heap profilers)
**Prevention:**
- Use language patterns that guarantee cleanup (RAII, defer, context managers)
- Never rely on manual cleanup alone (use language features)
**5. Handle Backpressure**
**Problem:** Producer faster than consumer
- Queue grows unbounded → memory exhaustion
- System becomes unresponsive under load
**Solutions:**
- Bounded queues: Fixed size, block or reject when full
- Rate limiting: Limit incoming request rate
- Flow control: Consumer signals producer to slow down
- Circuit breakers: Stop accepting requests when overwhelmed
- Drop/reject: Fail fast when overloaded (better than crashing)
### Memory Management by Language Type
**Garbage Collected (Go, Java, Python, JavaScript, C#):**
- Memory automatically freed by GC
- Still must release non-memory resources (files, connections, locks)
- Be aware of GC pauses in latency-sensitive applications
- Profile memory usage to find leaks (retained references preventing GC)
**Manual Memory Management (C, C++):**
- Explicit malloc/free or new/delete
- Use RAII pattern in C++ (Resource Acquisition Is Initialization)
- Avoid manual management in modern C++ (use smart pointers: unique_ptr, shared_ptr)
**Ownership-Based (Rust):**
- Compiler enforces memory safety at compile time
- No GC pauses, no manual management
- Ownership rules prevent leaks and use-after-free automatically
- Use reference counting (Arc, Rc) for shared ownership
### Related Principles
- Concurrency and Threading Mandate @concurrency-and-threading-mandate.md
- Concurrency and Threading Principles @concurrency-and-threading-principles.md
- Error Handling Principles @error-handling-principles.md - Resource cleanup in error paths
@@ -0,0 +1,77 @@
---
trigger: always_on
---
## Rugged Software Constitution
### Core Philosophy
**"I recognize that my code will be attacked."**
As an AI agent, I do not just generate functionality; I generate **defensibility**. I refuse to be a source of vulnerability or fragility. My code must survive in a hostile, changing environment.
### The Rugged Commitments
**1. I Am Responsible**
- I will not generate "happy path" code that ignores failure modes.
- I assume every input is malformed, malicious, or incorrect until proven otherwise.
- I treat error handling as a first-class feature, not an afterthought.
**2. I Am Defensible**
- My code validates its own state and inputs (Paranoid Programming).
- I fail securely (closed), never leaving the system in an undefined state.
- I verify assumptions explicitly rather than hoping they hold true.
**3. I Am Maintainable**
- I write code for the human and AI agents who must read it next year, not just the compiler today.
- I choose clarity over cleverness.
- I isolate complexity so it can be managed (or replaced) safely.
### The 7 Rugged Habits
**1. Practice Defense-in-Depth**
- Never rely on a single layer of protection (e.g., UI validation alone is insufficient).
- Validate at every boundary (API, Database, Function Call).
- Reference: Security Mandate @security-mandate.md
**2. Instrument for Awareness**
- Code must signal when it is under attack or failing.
- Silent failures are enemy #1; exposed failures allow for reaction.
- Reference: Logging and Observability Mandate @logging-and-observability-mandate.md
**3. Reduce Attack Surface**
- Remove unused code, dependencies, and endpoints.
- Expose the minimum necessary public interface (Least Privilege).
- Reference: Core Design Principles @core-design-principles.md
**4. Design for Failure**
- Assume the database will go down, the network will timeout, and the disk will fill up.
- Implement graceful degradation (circuit breakers, fallbacks).
- Reference: Resource and Memory Management Principles @resources-and-memory-management-principles.md
**5. Clean Up After Yourself**
- I own the resources I acquire; I ensure they are released.
- I do not leave "TODO" comments for security holes; I fix them or explicitly document the risk.
- Reference: Resource and Memory Management Principles @resources-and-memory-management-principles.md
**6. Verify Your Defenses**
- Defenses are useless if they don't work; tests prove they work.
- Test the "unhappy path" (attacks, errors, edge cases) as rigorously as the happy path.
- Reference: Testing Strategy @testing-strategy.md
**7. Adapt to the Ecosystem**
- Use established, battle-tested libraries over custom implementations.
- Follow community conventions to ensure long-term maintainability.
- Reference: Code Idioms and Conventions @code-idioms-and-conventions.md
### Application to Code Generation
**When generating code, I will:**
- **Refuse** to generate insecure patterns (SQLi, hardcoded secrets, shell injection), even if asked.
- **Proactively** add validation, error handling, and timeout logic, even if not explicitly requested.
- **Explain** *why* I added a defensive measure (e.g., "Added input validation to prevent XSS").
### Related Principles
- Security Mandate @security-mandate.md
- Error Handling Principles @error-handling-principles.md
- Architectural Patterns — Testability-First Design @architectural-pattern.md
+41
View File
@@ -0,0 +1,41 @@
---
trigger: always_on
---
## Rule Priority (When Rules Conflict)
When two rules pull in opposite directions, use this priority to decide:
### Priority Order (Highest to Lowest)
1. **Security Mandate** — always wins. Never compromise security for velocity, simplicity, or convenience.
2. **Rugged Software Constitution** — foundational philosophy. Code must be defensible.
3. **Code Completion Mandate** and **Logging and Observability Mandate** — both are always-on enforcement rules. Validation and instrumentation are non-negotiable; neither can be skipped to ship faster.
4. **Testability-First Design** — maintainability enables future improvements.
5. **Feature-specific principles** — context-dependent guidance for the task at hand. This includes language-specific idiom files (`go-idioms-and-patterns.md`, `typescript-idioms-and-patterns.md`, `vue-idioms-and-patterns.md`, `flutter-idioms-and-patterns.md`, `rust-idioms-and-patterns.md`), `concurrency-and-threading-mandate.md`, and `ci-cd-principles.md`. When an idiom conflicts with a higher-priority rule (e.g., security or testability), the higher-priority rule always wins.
6. **PRD-gated principles** — only apply when the PRD or technical architecture document *explicitly requires* the capability. Includes `feature-flags-principles.md` and `ci-cd-gitops-kubernetes.md`. Must not be applied based on speculation; agent must confirm requirement before activating.
7. **YAGNI / KISS** — only when no security, reliability, or maintainability trade-off exists.
### Common Conflict Resolutions
| Conflict | Resolution |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| YAGNI vs Security ("don't add input validation, it's not needed yet") | **Security wins.** Input validation is always needed. |
| KISS vs Testability ("adding an interface makes it more complex") | **Testability wins.** Interfaces enable testing, which enables maintainability. |
| Performance vs YAGNI ("should I optimize this now?") | **Measure first.** Only optimize after profiling shows a real bottleneck. |
| DRY vs Clarity ("should I abstract this into a shared utility?") | **Clarity wins** until duplication reaches 3+ instances (Rule of Three). |
| Speed vs Logging ("skip logging to ship faster") | **Logging wins.** Silent failures are the enemy. |
| YAGNI vs PRD-gated requirement ("feature flags aren't needed") | **PRD wins.** If explicitly required, YAGNI cannot override it. |
### Guiding Principle
When in doubt, ask: *"Which choice makes the code more defensible and maintainable?"*
If both options are equally defensible, choose the simpler one (KISS).
### Related Principles
- Security Mandate @security-mandate.md
- Rugged Software Constitution @rugged-software-constitution.md
- Code Completion Mandate @code-completion-mandate.md
- Logging and Observability Mandate @logging-and-observability-mandate.md
- Architectural Patterns — Testability-First Design @architectural-pattern.md
+194
View File
@@ -0,0 +1,194 @@
---
trigger: model_decision
description: When writing Rust code, working on a Rust or Cargo project, or reviewing Rust idioms and safety practices
---
## Rust Idioms and Patterns
### Core Philosophy
Rust's type system and ownership model are your primary tools for correctness. Lean into the compiler — it is your strongest ally. Write code that is idiomatic, safe, and expressive.
> **Scope:** This file covers Rust-specific *coding idioms*. For file layout, see `project-structure-rust-cargo.md`. For test naming conventions, see `testing-strategy.md`. For logging library choice, see `logging-and-observability-principles.md`.
### Ownership and Borrowing
1. **Prefer borrowing (`&T`, `&mut T`) over cloning**
- Never `.clone()` to silence the borrow checker without a `// CLONE:` comment explaining why
- Use `Cow<'_, T>` when a function may or may not need ownership
- Prefer `&str` over `String` in function parameters, `&[T]` over `Vec<T>`
2. **Minimize owned data in structs**
- Use references with explicit lifetimes when the struct is short-lived
- Use owned types (`String`, `Vec<T>`) when the struct must outlive its inputs
3. **Avoid unnecessary `Arc<Mutex<T>>`**
- If data flows one direction, use channels (`tokio::sync::mpsc`)
- If data is read-heavy, consider `RwLock` over `Mutex`
- If data is immutable after init, use `Arc<T>` without a lock
### Error Handling
1. **Use the `?` operator for propagation — never `unwrap()` in production code**
- `unwrap()` and `expect()` are acceptable only in:
- Tests (`#[test]`, `#[tokio::test]`)
- Infallible operations where the invariant is proven (document with `// SAFETY:` comment)
- CLI `main()` function with clear error messages via `expect("reason")`
2. **Choose error crates by context:**
- Library code: `thiserror` — define typed error enums
- Application code: `anyhow` — ergonomic error chaining
- Never mix: library crates should not depend on `anyhow`
3. **Error type design:**
```rust
// ✅ Good — typed, matchable errors
#[derive(Debug, thiserror::Error)]
pub enum PathfinderError {
#[error("file not found: {path}")]
FileNotFound { path: PathBuf },
#[error("AST parse failed: {0}")]
ParseError(String),
#[error(transparent)]
Io(#[from] std::io::Error),
}
// ❌ Bad — stringly-typed, unmatchable
fn do_thing() -> Result<(), String> { ... }
// ✅ Annotate public Result-producing functions to force callers to handle them
#[must_use]
pub fn create_task(req: CreateTaskRequest) -> Result<Task, TaskError> { ... }
// Triggers a compiler warning when the return value is fully ignored:
// create_task(req); // warning: unused `Result` that must be used
//
// This does NOT trigger the warning (intentional discard — still valid when used deliberately):
// let _ = create_task(req); // explicit discard, silences warning by design
```
### Async and Concurrency
1. **Use `tokio` as the async runtime**
- Mark async entry points with `#[tokio::main]` or `#[tokio::test]`
- Prefer `tokio::spawn` for concurrent tasks, not `std::thread::spawn`
- Use `tokio::select!` for racing futures, not manual polling
2. **Cancellation safety:**
- Prefer `tokio::sync::mpsc` over `tokio::sync::broadcast` unless fan-out is needed
- Document cancellation behavior on any `async fn` that holds resources across `.await`
- Use `tokio_util::sync::CancellationToken` for graceful shutdown
3. **Blocking operations:**
- Never call blocking I/O inside async context
- Use `tokio::task::spawn_blocking` for CPU-heavy or blocking work
- Use `tokio::fs` instead of `std::fs` inside async functions
### Unsafe Code
1. **Zero `unsafe` blocks unless in FFI boundaries**
- Tree-sitter C bindings and similar FFI are the only valid use case
- Every `unsafe` block must have a `// SAFETY:` comment explaining the invariant
2. **Minimize unsafe surface area:**
- Encapsulate `unsafe` in a safe wrapper function
- The wrapper's public API must be safe to call from any context
- Write tests that exercise the boundary conditions of `unsafe` wrappers
3. **Never use `unsafe` to bypass the borrow checker** — restructure the code instead
### Lifetimes and Generics
1. **Prefer `'_` lifetime elision when possible**
- Only introduce named lifetimes when the compiler requires them or when they clarify intent
- Use `'a` for single lifetime parameters, descriptive names (`'input`, `'query`) for multiple
2. **Keep generic bounds simple:**
- Prefer concrete types for prototyping, introduce generics when the pattern stabilizes
- Use `impl Trait` in argument position for simple cases
- Use `where` clauses for complex bounds — never inline complex bounds in `<...>`
3. **Avoid lifetime gymnastics:**
- If lifetime annotations become complex, restructure to use owned data or `Arc`
- Consider the "split borrow" pattern to avoid borrow checker issues in struct methods
### Idiomatic Patterns
1. **Builder pattern** for types with many optional fields:
- Return `Self` from builder methods for chaining
- `build()` returns `Result<T, BuildError>`, not `T`
2. **Newtype pattern** for domain types:
- Wrap primitives: `struct UserId(u64)`, not bare `u64`
- Implement `Deref` only when the newtype truly "is-a" the inner type
3. **Typestate pattern** for state machines:
- Different states = different types — invalid transitions are compile errors
- Use this for protocol implementations and lifecycle management
4. **`From`/`Into` conversions:**
- Implement `From<A> for B` (never `Into` directly)
- Use `impl From<X> for Error` with `thiserror`'s `#[from]` attribute
### Testing
1. **Test organization (Rust-specific — differs from Go/TS):**
- **Unit tests:** `#[cfg(test)] mod tests` block **at the bottom of each `.rs` file** — this is the idiomatic Rust convention, not a shortcut
- Tests can access private functions via `use super::*`
- Code inside `#[cfg(test)]` is stripped from production builds
- Do NOT create separate `*_test.rs` files — this breaks private access and is non-idiomatic
- **Integration tests:** `tests/` directory at crate root (each file compiled as a separate crate)
- Only tests public API — use `use my_crate::function;`
- No `#[cfg(test)]` annotation needed
- Shared helpers: `tests/common/mod.rs` (NOT `tests/common.rs`, which Cargo treats as a test file)
- Use `#[tokio::test]` for async tests
2. **Test naming:** `fn test_<function>_<scenario>_<expected>()` (snake_case)
3. **Assertions:**
- Use `assert_eq!` / `assert_ne!` over `assert!(a == b)` — better error messages
- Use `assert!(matches!(result, Ok(_)))` for enum variant checking
4. **Property testing:** Use `proptest` or `quickcheck` for functions with wide input spaces
### Clippy and Formatting
1. **`cargo check` for fast iteration during development**
- `cargo check`: type-checks without producing a binary — fastest feedback loop
- `cargo clippy`: includes `cargo check` plus lint rules — use before committing
- `cargo build`: only when you need the actual binary/library artifact
- Never run `cargo build` during TDD cycles — it is significantly slower than `cargo check`
2. **`cargo clippy` must pass with zero warnings** before any commit
- Use `#[allow(clippy::...)]` only with a `// ALLOW:` comment explaining why
- Prefer fixing the lint over suppressing it
3. **`cargo fmt` is non-negotiable** — all code must be formatted
4. **Recommended project-level Clippy configuration (`.clippy.toml` or `Cargo.toml`):**
```toml
[lints.clippy]
pedantic = "warn"
unwrap_used = "deny"
expect_used = "warn"
```
### Dependency Management
1. **Minimize dependency count** — each dependency is an attack surface and compile-time cost
2. **Pin major versions** in `Cargo.toml` — use `dep = "1"` not `dep = "*"`
3. **Audit regularly** — run `cargo audit` to check for known vulnerabilities
4. **Prefer well-maintained crates** — check download count, last commit date, and issue tracker
### Related Principles
- Error Handling Principles @error-handling-principles.md
- Concurrency and Threading Principles @concurrency-and-threading-principles.md
- Concurrency and Threading Mandate @concurrency-and-threading-mandate.md
- Performance Optimization Principles @performance-optimization-principles.md
- Resource and Memory Management Principles @resources-and-memory-management-principles.md
- Security Mandate @security-mandate.md
- Code Idioms and Conventions @code-idioms-and-conventions.md
- Testing Strategy @testing-strategy.md
- Dependency Management Principles @dependency-management-principles.md
+19
View File
@@ -0,0 +1,19 @@
---
trigger: always_on
---
## Security Mandate
**Security is a foundational requirement, not a feature.**
### Universal Security Principles
1. **Never trust user input:** All data from users, APIs, or external sources must be validated server-side
2. **Deny by default:** Require explicit permission grants, never assume access
3. **Fail securely:** When errors occur, fail closed (deny access) rather than open
4. **Defense in depth:** Multiple layers of security, never rely on a single control
**When implementing security-sensitive features (auth, validation, queries), see Security Principles @security-principles.md for detailed implementation guidance.**
### Related Principles
- Security Principles @security-principles.md
+51
View File
@@ -0,0 +1,51 @@
---
trigger: model_decision
description: When implementing authentication, authorization, input validation, cryptographic operations, or handling user input and sensitive data
---
## Security Principles
### OWASP Top 10 Enforcement
* **Broken Access Control:** Deny by default. Validate permissions *server-side* for every request. Do not rely on UI state.
* **Cryptographic Failures:** Use TLS 1.2+ everywhere. Encrypt PII/Secrets at rest. Use standard algorithms (AES-256, RSA-2048, Ed25519). *Never* roll your own crypto.
* **Injection:** ZERO TOLERANCE for string concatenation in queries. Use Parameterized Queries (SQL) or ORM bindings. Sanitize all HTML/JS output.
* **SSRF Prevention:** Validate all user-provided URLs against an allowlist. Disable HTTP redirects in fetch clients. Block requests to internal IPs (metadata services, localhost).
* **Insecure Design:** Threat model every new feature. Fail securely (closed), not openly.
* **Vulnerable Components:** Pin dependency versions. Scan for CVEs in CI/CD.
### Authentication & Authorization
* **Passwords:** Hash with Argon2id or Bcrypt (min cost 12). Never plain text.
* **Tokens:**
* *Access Tokens:* Short-lived (15-30 mins). HS256 or RS256.
* *Refresh Tokens:* Long-lived (7-30 days). Rotate on use. Store in `HttpOnly; Secure; SameSite=Strict` cookies.
* **Rate Limiting:** Enforce strictly on public endpoints (Login, Register, Password Reset). Standard: 5 attempts / 15 mins.
* **MFA:** Required for Admin and Sensitive Data access.
* **RBAC:** Map permissions to Roles, not Users. Check permissions at the Route AND Resource level.
### Input Validation & Sanitization
* **Principle:** "All Input is Evil until Proven Good."
* **Validation:** Validate against a strict Schema (Zod/Pydantic) at the *Handler/Port* boundary.
* **Allowlist:** Check for "Good characters" (e.g., `^[a-zA-Z0-9]+$`), do not try to filter "Bad characters."
* **Sanitization:** Strip dangerous tags from rich text input using a proven library (e.g., DOMPurify equivalent).
### Logging & Monitoring (Security Focus)
* **Redaction:** SCRUB all PII, Secrets, Tokens, and Passwords from logs *before* writing.
* **Events:** Log all *failed* auth attempts, access denied events, and input validation failures.
* **Format:** JSON structured logs with `correlationId`, `user_id`, and `event_type`.
* **Anti-Tamper:** Logs should be write-only for the application.
### Secrets Management
* **Storage:** Never commit secrets to git. Use `.env` (local) or Secret Managers (Prod - e.g., Vault/GSM).
### Related Principles
- Error Handling Principles @error-handling-principles.md
- API Design Principles @api-design-principles.md
- Logging and Observability Mandate @logging-and-observability-mandate.md
- Logging and Observability Principles @logging-and-observability-principles.md
- Configuration Management Principles @configuration-management-principles.md
- Command Execution Principles @command-execution-principles.md
+169
View File
@@ -0,0 +1,169 @@
---
trigger: model_decision
description: When writing tests, organizing test files, implementing test doubles, or setting up testing infrastructure
---
## Testing Strategy
### Test Pyramid
**Unit Tests (70% of tests):**
- **What:** Test domain logic in isolation with mocked dependencies
- **Speed:** Fast (<100ms per test)
- **Scope:** Single function, class, or module
- **Dependencies:** All external dependencies mocked (repositories, APIs, time, random)
- **Coverage Goal:** >85% of domain logic
**Integration Tests (20% of tests):**
- **What:** Test adapters against real infrastructure
- **Speed:** Medium (100ms-5s per test)
- **Scope:** Component interaction with infrastructure (database, cache, message queue)
- **Dependencies:** Real infrastructure via Testcontainers
- **Coverage Goal:** All adapter implementations, critical integration points
**End-to-End Tests (10% of tests):**
- **What:** Test complete user journeys through all layers
- **Speed:** Slow (5s-30s per test)
- **Scope:** Full system from HTTP request to database and back
- **Dependencies:** Entire system running (or close approximation)
- **Coverage Goal:** Happy paths, critical business flows
### Test-Driven Development (TDD)
**Red-Green-Refactor Cycle:**
1. **Red:** Write a failing test for next bit of functionality
2. **Green:** Write minimal code to make test pass
3. **Refactor:** Clean up code while keeping tests green
4. **Repeat:** Next test
**Benefits:**
- Tests written first ensure testable design
- Comprehensive test coverage (code without test doesn't exist)
- Faster development (catch bugs immediately, not in QA)
- Better design (forces thinking about interfaces before implementation)
### Test Doubles Strategy
**Unit Tests:** Use mocks/stubs for all driven ports
- Mock repositories return pre-defined data
- Mock external APIs return successful responses
- Mock time/random for deterministic tests
- Control test environment completely
**Integration Tests:** Use real infrastructure
- Testcontainers spins up PostgreSQL, Redis, message queues
- Firebase emulator spins up Firebase Authentication, Cloud Firestore, Realtime Database, Cloud Storage for Firebase, Firebase Hosting, Cloud Functions, Pub/Sub, and Firebase Extensions
- Test actual database queries, connection handling, transactions
- Verify adapter implementations work with real services
**Best Practice:**
- Generate at least 2 implementations per driven port:
1. Production adapter (PostgreSQL, GCP GCS, etc.)
2. Test adapter (in-memory, fake implementation)
### Test Organization
**Universal Rule: Co-locate implementation tests; Separate system tests.**
**1. Unit & Integration Tests (Co-located)**
- **Rule:** Place tests **next to the file** they test.
- **Why:** Keeps tests visible, encourages maintenance, and supports refactoring (moving a file moves its tests).
- **Naming Convention Example:**
- **TS/JS:** `*.spec.ts` (Unit), `*.integration.spec.ts` (Integration)
- **Go:** `*_test.go` (Unit), `*_integration_test.go` (Integration)
- **Dart/Flutter:** `*_test.dart` (Unit), `*_integration_test.dart` (Integration) — tests live in `test/` mirroring `lib/` layout (Flutter's default convention, discovered by `flutter test`)
- **Python:** `test_*.py` (Unit), `test_*_integration.py` (Integration)
- **Java:** `*Test.java` (Unit), `*IT.java` (Integration)
- **Rust:** `#[cfg(test)] mod tests` inline in each `.rs` file (Unit), `tests/` directory at crate root (Integration)
> You must strictly follow the convention for the target language. Do not mix `test` and `spec` suffixes in the same application context.
> **Language-specific overrides:** Some ecosystems have different test location conventions. When a language-specific project structure file exists (e.g., `project-structure-flutter-mobile.md`, `project-structure-rust-cargo.md`), its test location rules take precedence over the co-location default. See `architectural-pattern.md` § Test co-location for the authoritative rule.
> **Rust exception:** Rust unit tests are **inline** (`#[cfg(test)] mod tests` at the bottom of each `.rs` file), not separate files. This is the official Rust convention — it enables testing private functions and is how `cargo test` discovers unit tests. Integration tests go in `tests/` at the crate root, compiled as separate crates. See `rust-idioms-and-patterns.md` for details.
**2. End-to-End Tests (Separate)**
- **Rule:** Place in a dedicated `e2e/` folder
- **Single app:** `e2e/` at project root
- **Monorepo:** `apps/e2e/` subdivided by test scope:
- `apps/e2e/api/` for full API flow E2E tests (HTTP → Database)
- `apps/e2e/ui/` for full-stack E2E tests (Browser → Backend → Database)
> Path adapts to project type per `project-structure.md` § Adapting for Different Project Types.
- **Naming:** Follow `{feature}-{ui/api}.e2e.test.{ext}` (Universal - configure test runner to match this pattern `**/*.e2e.test.*`)
- Example:
- `user-registration-api.e2e.test.ts` # Full API flow: HTTP → DB
- `user-registration-ui.e2e.test.ts` # Full-stack: Browser → Backend → DB
**Using Playwright MCP for UI E2E Tests:**
When running E2E tests interactively (during development or verification), use Playwright MCP:
```
# Navigate to the page
mcp_playwright_browser_navigate(url="http://localhost:5173/login")
# Capture accessible state (better than screenshot for assertions)
mcp_playwright_browser_snapshot()
# Interact with elements by ref from snapshot
mcp_playwright_browser_type(ref="<ref>", text="[email protected]")
mcp_playwright_browser_click(ref="<ref>")
# Wait for results
mcp_playwright_browser_wait_for(text="Welcome")
# Capture snapshot for walkthrough documentation (captures full accessibility tree)
mcp_playwright_browser_snapshot(filename="login-success.md")
```
**E2E Test Requirements:**
- Capture a snapshot (`mcp_playwright_browser_snapshot`) at each major step
- Save snapshots to walkthrough as proof of functionality
- If visual proof is needed, use `browser_subagent` with `RecordingName` to produce a recorded video artifact
- Test happy path AND at least one error path
- Clean up test data after test (or use unique identifiers)
**Key Principles:**
- **Unit/Integration tests**: Co-located with implementation
- **E2E tests**: Separate directory (crosses boundaries)
- **Test doubles**: Co-located with interface (storage_mock.go, taskAPI.mock.ts)
- **Pattern consistency**: All features follow same structure
### Test Quality Standards
**AAA Pattern (Arrange-Act-Assert):**
```
// Arrange: Set up test data and mocks
const user = { id: '123', email: '[email protected]' };
const mockRepo = createMockRepository();
// Act: Execute the code under test
const result = await userService.createUser(user);
// Assert: Verify expected outcome
expect(result.id).toBe('123');
expect(mockRepo.save).toHaveBeenCalledWith(user);
```
**Test Naming:**
- Descriptive: `should [expected behavior] when [condition]`
- Examples:
- `should return 404 when user not found`
- `should hash password before saving to database`
- `should reject email with invalid format`
**Coverage Requirements:**
- Unit tests: >85% code coverage
- Integration tests: All adapter implementations
- E2E tests: Critical user journeys
### Related Principles
- Architectural Patterns — Testability-First Design @architectural-pattern.md
- Error Handling Principles @error-handling-principles.md
- Project Structure @project-structure.md
@@ -0,0 +1,301 @@
---
trigger: model_decision
description: When writing TypeScript code, reviewing TypeScript idioms, or working on any TypeScript project (backend, frontend, or CLI)
---
## TypeScript Idioms and Patterns
### Core Philosophy
TypeScript's type system is your documentation, your test, and your specification — all at once. Make the type system encode the invariants of your domain so that invalid states are unrepresentable. Lean into the compiler.
> **Scope:** This file covers TypeScript-specific *type system and language idioms*. For Vue-specific patterns, see `vue-idioms-and-patterns.md`. For file layout, see `project-structure-vue-frontend.md`. For quality commands, see `code-completion-mandate.md`. For logging library, see `logging-and-observability-principles.md`.
---
### Strict Mode — Non-Negotiable
**Always enable strict mode** in `tsconfig.json`:
```json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
}
}
```
These flags catch the majority of runtime errors at compile time. Never disable them on a per-file basis without a `// STRICT-DISABLE:` comment explaining the rationale.
---
### Type System Idioms
1. **`unknown` over `any` — always**
```typescript
// ✅ Forces narrowing before use
function parse(data: unknown): User {
if (!isUser(data)) throw new Error('Invalid user shape');
return data;
}
// ❌ Disables the type checker entirely
function parse(data: any): User { return data; }
```
2. **Use `readonly` to enforce immutability at compile time**
```typescript
interface TaskState {
readonly id: string;
readonly items: readonly Task[];
}
// For function params that should not be mutated
function process(tasks: readonly Task[]): Summary { ... }
```
3. **Discriminated unions for type-safe state machines**
```typescript
type AsyncState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
// Exhaustive handling — compiler catches missing cases
function render(state: AsyncState<User>): string {
switch (state.status) {
case 'idle': return 'Waiting...';
case 'loading': return 'Loading...';
case 'success': return state.data.name; // data is typed
case 'error': return state.error.message;
}
}
```
4. **Const assertions for literal types**
```typescript
const ROLES = ['admin', 'editor', 'viewer'] as const;
type Role = typeof ROLES[number]; // 'admin' | 'editor' | 'viewer'
```
5. **Type narrowing — use type guards instead of `as` casts**
```typescript
// ✅ Type guard — safe narrowing
function isError(value: unknown): value is Error {
return value instanceof Error;
}
// ❌ Type assertion — bypasses type checker
const err = value as Error;
```
6. **Never use non-null assertion `!` in production code**
```typescript
// ❌ Hides a potential null/undefined bug
const name = user!.profile!.name;
// ✅ Explicit handling
const name = user?.profile?.name ?? 'Anonymous';
```
7. **`satisfies` operator for type-checked object literals (TS 4.9+)**
```typescript
// ✅ satisfies: compile-checked against interface, type stays as literal
const config = {
endpoint: '/api/tasks',
retries: 3,
} satisfies ApiConfig;
// config.retries is typed as `3` (literal), not `number` — narrower and safer
```
---
### Null Safety
1. **Prefer `??` (nullish coalescing) over `||` for default values**
```typescript
// ✅ Only falls back for null/undefined
const count = input.count ?? 0;
// ❌ Also falls back for 0, '', false
const count = input.count || 0;
```
2. **Use optional chaining `?.` for safe navigation**
```typescript
const city = user?.address?.city;
```
3. **Distinguish `undefined` (absence) from `null` (explicit empty)**
- Use `undefined` for optional fields
- Use `null` only when you need to represent "intentionally empty" on the wire (JSON APIs)
---
### Async/Await
> For general async patterns (when to use concurrency), see `concurrency-and-threading-mandate.md`. This section covers TypeScript-specific async idioms.
1. **Always `await` or handle returned Promises — no floating promises**
```typescript
// ❌ Fire-and-forget — errors are silently swallowed
sendEmail(user);
// ✅ Awaited
await sendEmail(user);
// ✅ Intentionally fire-and-forget needs explicit void annotation
void sendEmail(user); // still logs errors internally
```
2. **Use `Promise.all` for concurrent independent operations**
```typescript
// ✅ Concurrent — total time = max(individual times)
const [user, tasks] = await Promise.all([getUser(id), getTasks(id)]);
// ❌ Sequential — total time = sum of individual times
const user = await getUser(id);
const tasks = await getTasks(id);
```
3. **Use `Promise.allSettled` when partial failure is acceptable**
```typescript
const results = await Promise.allSettled(notifications.map(send));
const failed = results.filter(r => r.status === 'rejected');
```
4. **Never mix `async/await` with raw `.then()/.catch()` chains in the same function**
---
### Runtime Validation at Boundaries
**All data crossing a system boundary must be validated at runtime**, not just typed.
```typescript
import { z } from 'zod';
// Define schema as the single source of truth
const CreateTaskSchema = z.object({
title: z.string().min(1).max(200),
priority: z.enum(['low', 'medium', 'high']),
dueDate: z.string().datetime().optional(),
});
// Infer the TypeScript type from the schema — no duplication
type CreateTaskRequest = z.infer<typeof CreateTaskSchema>;
// Validate at the API boundary
function parseCreateTask(body: unknown): CreateTaskRequest {
return CreateTaskSchema.parse(body); // throws ZodError on invalid input
}
```
- Use `zod` for runtime schema validation at API ingress and external API egress
- Never use TypeScript's `as` operator as a substitute for runtime validation
- Validate on ingress; trust validated types thereafter
---
### Centralized HTTP Client
**All outbound HTTP calls MUST go through the project's single, shared API client utility.**
Do not call `fetch()` or `axios()` directly in feature code. Route every request through the centralized client (e.g., `apiFetch`, `apiClient`, or equivalent).
**Why this matters:**
- Consistent auth header injection (token is attached in one place)
- Correlation-ID propagation — every request carries a traceable ID
- Centralized error normalization — uniform error shapes for all API failures
- Single place to add retries, timeouts, and request logging
```typescript
// ❌ Anti-pattern — bypass: no auth header, no correlation-ID, no logging
const res = await fetch('/api/tasks');
// ✅ Correct — use the shared client
import { apiFetch } from '@/infrastructure/apiFetch';
const res = await apiFetch('/api/tasks');
```
**Exception:** The centralized client itself may use raw `fetch` or `axios` internally — that is its implementation detail, not a bypass.
> The audit's Integration Contracts dimension (Phase 1.5, Dimension A) checks compliance with this rule. Any direct `fetch`/`axios` call outside the shared client is a `[INT]` finding.
---
### Module and Export Patterns
1. **Prefer named exports over default exports**
```typescript
// ✅ Named — explicit, refactor-safe, IDE-friendly
export function createTask() { ... }
export type { Task };
// ❌ Default — ambiguous import names, harder to auto-import
export default function createTask() { ... }
```
2. **Avoid barrel re-exports that create circular dependency risk**
- Use feature `index.ts` files only for the public API of a feature, not as catch-all re-exports
3. **Import type separately to avoid bundling runtime artifacts**
```typescript
import type { Task } from './types';
```
---
### Testing
> Test naming, file conventions, and pyramid proportions are defined in `testing-strategy.md`. This section covers TypeScript-specific tooling.
1. **Type your mocks with Vitest types** — never use `as any` in test doubles
```typescript
import { vi } from 'vitest';
import type { MockedObject } from 'vitest';
const mockStore: MockedObject<TaskStore> = {
create: vi.fn(),
getById: vi.fn(),
};
```
2. **Assert on error types, not just error messages**
```typescript
await expect(service.create(invalid)).rejects.toThrow(ZodError);
```
3. **Use `satisfies` operator in tests for type-checked fixtures**
```typescript
const fixture = {
id: 'abc', title: 'Test task'
} satisfies Task;
```
---
### Formatting and Static Analysis
| Tool | Purpose | Notes |
| -------------------------- | --------------------------------------- | ----------------------------------------------------------------- |
| `vue-tsc --noEmit` | Full type checking (incl. `.vue` files) | Must pass zero errors; use `tsc --noEmit` for non-Vue TS projects |
| `eslint` | Lint rules + style | Use `@typescript-eslint/recommended-type-checked` |
| `prettier` | Canonical formatting | Non-negotiable |
| `npm audit` / `pnpm audit` | Dependency CVE scanning | Run in CI; fail on high severity |
See `code-completion-mandate.md` for the exact commands to run before committing.
---
### Related Principles
- Code Idioms and Conventions @code-idioms-and-conventions.md
- Vue Idioms and Patterns @vue-idioms-and-patterns.md
- Testing Strategy @testing-strategy.md
- Error Handling Principles @error-handling-principles.md
- Concurrency and Threading Mandate @concurrency-and-threading-mandate.md
- Security Principles @security-principles.md
- Dependency Management Principles @dependency-management-principles.md
+383
View File
@@ -0,0 +1,383 @@
---
trigger: model_decision
description: When writing Vue 3 code, working on Vue components, using Pinia stores, or building Vue composables
---
## Vue Idioms and Patterns
### Core Philosophy
Vue 3 Composition API is the default for all new code. `<script setup>` is the canonical syntax. Think in terms of reactive *data flows*, not component lifecycle hooks. Composables (`use*` functions) are the primary unit of logic reuse.
> **Scope:** This file covers Vue 3 *coding idioms* for components, stores, and composables. For TypeScript type system patterns, see `typescript-idioms-and-patterns.md`. For file and folder layout, see `project-structure-vue-frontend.md`. For test naming, see `testing-strategy.md`. For logging, see `logging-and-observability-principles.md`.
---
### `<script setup>` — The Only Style
Always use `<script setup lang="ts">`. Never use the Options API or the class-style component pattern for new code.
```vue
<!-- ✅ Canonical style -->
<script setup lang="ts">
import { ref, computed } from 'vue';
const props = defineProps<{ title: string; count?: number }>();
const emit = defineEmits<{ 'update:count': [value: number] }>();
const doubled = computed(() => (props.count ?? 0) * 2);
</script>
<!-- ❌ Options API — do not use for new components -->
<script lang="ts">
export default { props: { title: String }, ... }
</script>
```
---
### Reactivity: `ref` vs `reactive`
| Use | When |
| ------------ | ---------------------------------------------------------------------------------- |
| `ref<T>()` | Primitives, single values, values that may be reassigned |
| `reactive()` | Plain objects where you always access properties (never reassign the whole object) |
| `readonly()` | Expose state that must not be mutated outside its owner |
```typescript
// ✅ ref for primitives and replaceable objects
const count = ref(0);
const user = ref<User | null>(null);
user.value = fetchedUser; // reassignment is fine
// ✅ reactive for objects where you destructure properties
const form = reactive({ title: '', priority: 'medium' });
// ❌ Never destructure a reactive object — reactivity is lost
const { title } = form; // title is now a plain string, NOT reactive
// ✅ Use toRefs if you must destructure
const { title } = toRefs(form);
```
---
### Computed Properties
1. **Use `computed` for all derived state** — never recompute in the template
```typescript
// ✅ Cached, reactive
const filteredTasks = computed(() =>
tasks.value.filter(t => t.status === activeFilter.value)
);
// ❌ Recomputes on every render
// <template>{{ tasks.filter(t => t.status === filter) }}</template>
```
2. **Never cause side effects inside `computed`** — computed must be pure
```typescript
// ❌ Side effect in computed
const count = computed(() => {
taskStore.logAccess(); // NO — this is a side effect
return tasks.value.length;
});
```
3. **Use writable computed for two-way bindings**
```typescript
const modelValue = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val),
});
```
---
### Watch Strategy
Use the most precise watcher for the situation — over-watching is a performance and correctness problem.
| Watcher | Use When |
| ------------- | --------------------------------------------------------------------------------------------------------- |
| `watchEffect` | Side effect that should re-run whenever any of its reactive dependencies change; auto-tracks dependencies |
| `watch` | You need the old value, lazy execution, or want to watch a specific source explicitly |
| `computed` | You need a synchronous derived value (prefer this over `watch` for transformation) |
```typescript
// ✅ watchEffect — auto-tracks dependencies
watchEffect(() => {
document.title = `Tasks (${count.value})`;
});
// ✅ watch — explicit source, has old value
watch(userId, async (newId, oldId) => {
if (newId !== oldId) await loadUser(newId);
}, { immediate: true });
// ❌ Avoid using watch just for computed values
watch(tasks, () => { filteredCount.value = tasks.value.filter(...).length; });
// ✅ Use computed instead
const filteredCount = computed(() => tasks.value.filter(...).length);
```
---
### Pinia Stores
> The store directory structure is defined in `project-structure-vue-frontend.md`. This section covers Pinia coding idioms.
1. **Use the Setup Store API** (not Options API) for new stores
```typescript
// task/store/task.store.ts
export const useTaskStore = defineStore('task', () => {
// State
const tasks = ref<Task[]>([]);
const isLoading = ref(false);
// Getters (computed)
const completedTasks = computed(() =>
tasks.value.filter(t => t.status === 'done')
);
// Actions
async function loadTasks() {
isLoading.value = true;
try {
tasks.value = await taskAPI.getTasks();
} finally {
isLoading.value = false;
}
}
return { tasks, isLoading, completedTasks, loadTasks };
});
```
2. **Never mutate store state from outside the store**
```typescript
// ❌ Direct mutation from a component
const store = useTaskStore();
store.tasks.push(newTask); // NO
// ✅ Call an action
await store.addTask(newTask);
```
3. **Inject the API dependency — never import it directly inside the store**
```typescript
// ✅ Receives the API interface — testable with createTestingPinia + mock API
export const useTaskStore = defineStore('task', () => {
const api = inject<TaskAPI>(TASK_API_KEY);
if (!api) throw new Error('[TaskStore] TASK_API_KEY not provided — ensure app.provide() is called before store access');
// ...
});
```
4. **Use `storeToRefs` when destructuring a store in components**
```typescript
// ✅ Preserves reactivity
const { tasks, isLoading } = storeToRefs(useTaskStore());
const { loadTasks } = useTaskStore(); // actions don't need storeToRefs
```
---
### Composables (`use*` Functions)
Composables are the Vue equivalent of custom hooks — self-contained, reusable units of reactive logic.
1. **Naming: always prefix with `use`**
- `useTaskFilters`, `useAuth`, `usePagination`
2. **Return reactive refs, not raw values**
```typescript
// ✅ Caller can use returned values reactively
function useCounter(initial = 0) {
const count = ref(initial);
const increment = () => count.value++;
return { count, increment };
}
// ❌ count is a plain number — not reactive
function useCounter() {
let count = 0;
return { count };
}
```
3. **Always clean up side effects in `onUnmounted`**
```typescript
function useWindowResize() {
const width = ref(window.innerWidth);
const handler = () => (width.value = window.innerWidth);
onMounted(() => window.addEventListener('resize', handler));
onUnmounted(() => window.removeEventListener('resize', handler)); // ✅ cleanup
return { width };
}
```
4. **Template refs with `useTemplateRef` (Vue 3.5+)** — type-safe, IDE-friendly replacement for `ref(null)`
```typescript
// ✅ Vue 3.5+ — useTemplateRef provides fully typed access
const inputEl = useTemplateRef<HTMLInputElement>('myInput');
// <input ref="myInput" />
// ❌ Old pattern (before 3.5) — less type-safe
const inputEl = ref<HTMLInputElement | null>(null);
```
5. **Feature-specific composables live inside the feature directory** — global composables go in `src/composables/`. See `project-structure-vue-frontend.md`.
---
### Component Design
1. **`defineProps` with TypeScript generics — no runtime validators for typed props**
```typescript
const props = defineProps<{
taskId: string;
variant?: 'compact' | 'full';
}>();
// Defaults via withDefaults
const props = withDefaults(defineProps<{ variant?: 'compact' | 'full' }>(), {
variant: 'full',
});
```
2. **`defineEmits` with typed event signatures**
```typescript
const emit = defineEmits<{
'update:modelValue': [value: string];
'submit': [task: CreateTaskRequest];
}>();
```
3. **`v-model` contract: always `modelValue` prop + `update:modelValue` emit**
4. **`defineExpose` to selectively expose methods to parent refs**
```typescript
// Everything in <script setup> is private by default.
// Use defineExpose only for intentional parent access (e.g., form.reset()).
defineExpose({ reset, focus });
// ❌ Without defineExpose: parent ref.value.reset() will be undefined
```
5. **`v-bind="$attrs"` and `inheritAttrs: false` for forwarding attributes**
```typescript
// Avoid prop drilling for HTML attributes — forward them to the root element
defineOptions({ inheritAttrs: false });
// In template: <input v-bind="$attrs" />
```
6. **One concern per component** — if the template exceeds 100 lines (excluding boilerplate), extract a sub-component
7. **Never put business logic in the template** — computed and composables belong in `<script setup>`
---
### Template Patterns
1. **Always bind `:key` with stable, unique IDs in `v-for`** — never use index as key when list order can change
```html
<!-- ✅ Stable key -->
<TaskCard v-for="task in tasks" :key="task.id" :task="task" />
<!-- ❌ Index key — causes rerender bugs when list reordered -->
<TaskCard v-for="(task, i) in tasks" :key="i" :task="task" />
```
2. **Never combine `v-if` and `v-for` on the same element** — wrap with `<template>`
```html
<!-- ✅ -->
<template v-for="task in tasks" :key="task.id">
<TaskCard v-if="task.visible" :task="task" />
</template>
```
---
### Route Transitions
When using `<Transition>` or `<RouterView>` with transition effects, CSS frameworks that use `@layer` (Tailwind v4, Open Props, UnoCSS) can silently break SPA navigation by overriding transition properties in the cascade. This causes `transitionend` to never fire, permanently blocking the entering component.
1. **Avoid `mode="out-in"` when using `@layer`-based CSS frameworks** — the leaving component's `transitionend` event may never fire, blocking the entering component indefinitely. Use simultaneous transitions instead:
```html
<!-- ❌ Dangerous with @layer CSS frameworks -->
<Transition name="fade" mode="out-in">
<component :is="Component" />
</Transition>
<!-- ✅ Safe: simultaneous leave/enter, always mounts new component -->
<Transition name="fade">
<component :is="Component" :key="$route.path" />
</Transition>
```
2. **Always bind `:key="$route.path"`** on dynamic `<component>` inside `<Transition>` — forces Vue to treat each route as a distinct component instance, ensuring proper enter/leave lifecycle
3. **Use `!important` on route transition CSS classes** — guarantees transition properties win the `@layer` cascade:
```css
.fade-enter-active {
transition: opacity 0.15s ease-in !important;
}
.fade-leave-active {
transition: opacity 0.15s ease-out !important;
position: absolute !important;
width: 100% !important;
top: 0 !important;
left: 0 !important;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0 !important;
}
```
4. **Give the transition parent `position: relative`** — contains the absolutely-positioned leaving element during the simultaneous transition overlap
> For full diagnosis steps when a transition-stuck blank screen occurs, see the Debugging Protocol's [Frontend module](file:///home/irahardianto/works/projects/awesome-agv/.agents/skills/debugging-protocol/languages/frontend.md) § CSS × Animation.
---
### Testing
> Test naming and pyramid proportions are defined in `testing-strategy.md`. This section covers Vue-specific tooling.
1. **Use `createTestingPinia` to stub stores in component tests**
```typescript
import { vi } from 'vitest';
const wrapper = mount(TaskView, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn })],
},
});
```
2. **Test component behaviour, not implementation details** — query by accessible role, not CSS class
3. **Test stores independently** — use `setActivePinia(createPinia())` in store unit tests
---
### Linting and Type Checking
| Tool | Purpose |
| ------------------- | --------------------------- |
| `vue-tsc --noEmit` | Full-template type checking |
| `eslint-plugin-vue` | Vue-specific lint rules |
| `prettier` | Canonical formatting |
See `code-completion-mandate.md` for exact commands.
---
### Related Principles
- Code Idioms and Conventions @code-idioms-and-conventions.md
- TypeScript Idioms and Patterns @typescript-idioms-and-patterns.md
- Project Structure — Vue Frontend @project-structure-vue-frontend.md
- Architectural Patterns — Testability-First Design @architectural-pattern.md
- Testing Strategy @testing-strategy.md
- Logging and Observability Principles @logging-and-observability-principles.md