[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
+91
View File
@@ -0,0 +1,91 @@
---
name: adr
description: Architecture Decision Record skill for documenting significant architectural decisions with context, options, and consequences. Use during the Research phase when choosing between approaches, or whenever the user asks to document an architectural decision.
---
# Architecture Decision Record (ADR) Skill
## Purpose
Document significant architectural decisions so institutional knowledge persists across conversations and team members. ADRs capture the **why**, not just the **what**.
## When to Invoke
- During Research phase (`/1-research`) when a significant architecture decision is identified
- When user explicitly asks to document a decision
- When choosing between 2+ viable approaches
- When introducing a new dependency or pattern
- When changing existing architecture
## ADR Storage
ADRs are stored in `docs/decisions/` as numbered files:
```
docs/decisions/
├── 0001-use-postgresql-for-storage.md
├── 0002-adopt-feature-based-structure.md
├── 0003-use-testcontainers-for-integration.md
└── NNNN-short-title.md
```
## ADR Template
Create the ADR file at `docs/decisions/NNNN-short-title.md`:
```markdown
# NNNN. Short Title
**Date:** YYYY-MM-DD
**Status:** Proposed | Accepted | Deprecated | Superseded by NNNN
## Context
What is the issue that we're seeing that is motivating this decision?
Include technical constraints, business requirements, and relevant context.
## Options Considered
### Option A: {name}
- **Pros:** ...
- **Cons:** ...
- **Effort:** Low/Medium/High
### Option B: {name}
- **Pros:** ...
- **Cons:** ...
- **Effort:** Low/Medium/High
### Option C: {name} (if applicable)
- **Pros:** ...
- **Cons:** ...
- **Effort:** Low/Medium/High
## Decision
We chose **Option X** because...
## Consequences
### Positive
- What becomes easier or possible as a result
### Negative
- What becomes harder as a result
- Technical debt introduced (if any)
### Risks
- What could go wrong with this decision
- Mitigation strategies
## Related
- Links to relevant rules, previous ADRs, or external resources
```
## Process Guidelines
1. **Number sequentially** — check existing ADRs in `docs/decisions/` for the next number
2. **Keep titles short** — descriptive enough to identify the decision at a glance
3. **Status lifecycle:** `Proposed` → `Accepted` (after approval) → optionally `Deprecated` or `Superseded`
4. **Never delete ADRs** — if a decision is reversed, mark as `Superseded by NNNN` and create a new ADR
5. **Use Sequential Thinking skill** if the trade-off analysis is complex
## Rule Compliance
ADRs should reference applicable rules:
- Architectural Patterns @architectural-pattern.md
- Core Design Principles @core-design-principles.md
- Project Structure @project-structure.md
+140
View File
@@ -0,0 +1,140 @@
---
name: code-review
description: Structured code review protocol for inspecting code quality against the full rule set. Use when auditing code written by yourself or another agent, during the /audit workflow, or when the user asks for a code review.
---
# Code Review Skill
## Purpose
Systematically review code against the full antigravity rule set. Catches issues that linters miss: architectural violations, missing observability, business logic errors, pattern inconsistencies.
## When to Invoke
- During the `/audit` workflow (Phase 1: Code Review)
- When user asks for a code review outside any workflow
- **Best practice:** Invoke in a fresh conversation (not the same one that authored the code) to avoid confirmation bias
## Review Process
### 1. Scope the Review
Identify the files/features to review. Determine the review scope:
- **Feature review** — all files in a feature directory
- **PR review** — only changed files
- **Full codebase audit** — all features
### 2. Load the Rule Set
Read all applicable rules from `.agents/rules/`. Use `rule-priority.md` for severity classification.
### 3. Review Categories (Priority Order)
Review each file/feature against these categories, in order from `rule-priority.md`:
#### Critical (Must Fix)
- **Security** — injection, hardcoded secrets, broken auth
- **Data loss** — missing error handling on writes, no transaction boundaries
- **Resource leaks** — unclosed connections, missing cleanup
#### Major (Should Fix)
- **Testability** — I/O not behind interfaces, untested error paths
- **Observability** — missing logging on operations, no correlation IDs
- **Error handling** — empty catch blocks, swallowed errors
- **Architecture** — circular dependencies, wrong layer access
#### Minor (Nice to Fix)
- **Pattern consistency** — deviation from established codebase patterns
- **Naming** — unclear variable/function names
- **Code organization** — functions too long, mixed responsibilities
#### Nit (Optional)
- **Style** — formatting issues the linter would catch
- **Documentation** — missing comments on complex logic
### 4. Produce Findings
Output a structured findings document:
```markdown
# Code Review: {Feature/Module Name}
Date: {date}
Reviewer: AI Agent (fresh context)
## Summary
- **Files reviewed:** N
- **Issues found:** N (X critical, Y major, Z minor, W nit)
## Critical Issues
- [ ] **[SEC]** {description} — [{file}:{line}](file:///path)
- [ ] **[DATA]** {description} — [{file}:{line}](file:///path)
## Major Issues
- [ ] **[TEST]** {description} — [{file}:{line}](file:///path)
- [ ] **[OBS]** {description} — [{file}:{line}](file:///path)
## Minor Issues
- [ ] **[PAT]** {description} — [{file}:{line}](file:///path)
## Nit
- [ ] {description} — [{file}:{line}](file:///path)
## Rules Applied
List of rules referenced during this review.
```
### 5. Save the Report
When invoked via the `/audit` workflow, you **MUST** persist the findings to the repo:
**Path:** `docs/audits/review-findings-{feature}-{YYYY-MM-DD}-{HHmm}.md`
1. Create `docs/audits/` if it doesn't exist
2. Write the findings document to that path
3. This makes the report accessible from other conversations and agents
When invoked as a standalone review (not via `/audit`), saving to `docs/audits/` is recommended but optional.
### 6. Severity Tags
| Tag | Category | Rule Source |
| -------- | -------------------- | -------------------------------------------------- |
| `[SEC]` | Security | `security-principles.md` |
| `[DATA]` | Data integrity | `error-handling-principles.md` |
| `[RES]` | Resource leak | `resources-and-memory-management-principles.md` |
| `[TEST]` | Testability | `architectural-pattern.md`, `testing-strategy.md` |
| `[OBS]` | Observability | `logging-and-observability-mandate.md` |
| `[ERR]` | Error handling | `error-handling-principles.md` |
| `[ARCH]` | Architecture | `architectural-pattern.md`, `project-structure.md` |
| `[PAT]` | Pattern consistency | `code-organization-principles.md` |
| `[INT]` | Integration contract | `api-design-principles.md` |
| `[DB]` | Database design | `database-design-principles.md` |
| `[CFG]` | Configuration | `configuration-management-principles.md` |
### 7. Language-Specific Anti-Patterns
Load the anti-pattern checklist for the language(s) under review:
| Language | Anti-Patterns |
|---|---|
| **Go** | `languages/go.md` |
| **TypeScript** | `languages/typescript.md` *(placeholder — create when needed)* |
| **Flutter/Dart** | `languages/flutter.md` *(placeholder — create when needed)* |
| **Rust** | `languages/rust.md` *(placeholder — create when needed)* |
> Anti-patterns listed in language files are **auto-fail** — they require no judgment call. If the pattern exists in the code, it is a finding.
### 8. Cross-Boundary Checks
For full audits, cross-boundary concerns (integration contracts, database schema, configuration hygiene, dependency health, test coverage gaps) are checked via the dedicated dimension checklist in the `/audit` workflow — **Phase 1.5: Cross-Boundary Review**.
When invoking this skill standalone (outside `/audit`), apply the applicable dimensions from that checklist manually and tag findings with `[INT]`, `[DB]`, or `[CFG]` as appropriate.
**Zero-Findings Guard:** If this review produces fewer than 3 findings, you MUST produce a "Dimensions Covered" attestation section in the findings document, listing each cross-boundary dimension and the specific files or queries you examined. Only then may you declare a clean result.
---
## Rule Compliance
This skill enforces all rules in `.agents/rules/`. Key references:
- Rule Priority @rule-priority.md (severity classification)
- Security Principles @security-principles.md
- Architectural Patterns @architectural-pattern.md
- Testing Strategy @testing-strategy.md
- Logging and Observability Mandate @logging-and-observability-mandate.md
- Error Handling Principles @error-handling-principles.md
@@ -0,0 +1,89 @@
# Go Anti-Patterns (Auto-Fail)
> **Load this file** during code reviews of Go code. Patterns listed here are **immediate findings** — no judgment needed. If the pattern exists, it is a finding.
---
## Auto-Fail Table
| Pattern | Severity | Tag | Correct Fix |
|---|---|---|---|
| `defer X.Close()` (bare) | Critical | `[RES]` | Error-checked closure with `slog.Warn` |
| `//nolint:errcheck` | Critical | `[ERR]` | Handle the error — use error-checked closure |
| `defer tx.Rollback()` (bare) | Critical | `[RES]` | Error-checked closure with `sql.ErrTxDone` guard |
| `fmt.Printf` / `fmt.Println` | Major | `[OBS]` | Replace with `slog` structured logging |
| `log.Printf` / `log.Println` | Major | `[OBS]` | Replace with `slog` structured logging |
| `resp.Body.Close()` without drain | Major | `[RES]` | `io.Copy(io.Discard, resp.Body)` then close |
| Empty `if err != nil {}` block | Critical | `[ERR]` | Handle, return, or log the error |
| `_ = someFunc()` (discarded error) | Major | `[ERR]` | Handle the error explicitly |
---
## Detection Commands
Use these grep patterns to scan for anti-patterns before manual review:
```bash
# Bare defer Close/Rollback (no error check)
grep -rn 'defer.*\.Close()' --include='*.go'
grep -rn 'defer.*\.Rollback()' --include='*.go'
# nolint:errcheck suppression
grep -rn 'nolint:errcheck' --include='*.go'
# Unstructured logging in production code
grep -rn 'fmt\.Printf\|fmt\.Println\|log\.Printf\|log\.Println' --include='*.go' | grep -v '_test.go'
# Discarded errors
grep -rn '_ = ' --include='*.go' | grep -v '_test.go'
```
---
## Correct Patterns (Reference)
### Resource Cleanup
```go
// ✅ Rows / Statements / Connections
defer func() {
if err := rows.Close(); err != nil {
slog.Warn("failed to close rows", "error", err, "operation", "<operation>")
}
}()
// ✅ Transaction Rollback
defer func() {
if err := tx.Rollback(); err != nil && !errors.Is(err, sql.ErrTxDone) {
slog.Error("failed to rollback transaction", "error", err, "operation", "<operation>")
}
}()
// ✅ HTTP Response Body
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)
}
}()
```
### Structured Logging
```go
// ❌ fmt.Printf("User %s logged in from %s\n", userID, ip)
// ❌ log.Printf("User %s logged in from %s", userID, ip)
// ✅
slog.Info("user login", "userId", userID, "ip", ip)
```
---
## References
- Go Idioms and Patterns @go-idioms-and-patterns.md
- Logging and Observability Principles @logging-and-observability-principles.md
- Error Handling Principles @error-handling-principles.md
- Resources and Memory Management Principles @resources-and-memory-management-principles.md
@@ -0,0 +1,93 @@
---
name: debugging-protocol
description: Comprehensive protocol for validating root causes of software issues. Use when you need to systematically debug a complex bug, flaky test, or unknown system behavior by forming hypotheses and validating them with specific tasks.
---
# Debugging Protocol
## Overview
This skill provides a rigorous framework for debugging complex software issues. It moves beyond ad-hoc troubleshooting to a structured process of hypothesis generation and validation.
Use this skill to:
1. Formalize a debugging session.
2. Systematically eliminate potential root causes.
3. Document findings for future reference or team communication.
## Protocol Workflow
To run a structured debugging session, follow these steps:
### 1. Initialize the Session
Create a new debugging document using the provided template. This serves as the "source of truth" for the investigation.
**Template location:** `assets/debugging-session-template.md`
**Save to:** `docs/debugging/{issue-name}-{YYYY-MM-DD}-{HHmm}.md`
1. Create `docs/debugging/` if it doesn't exist
2. Copy the template and fill in the issue details
3. This makes the session accessible from other conversations and agents (e.g., when handing off to a `/quick-fix` or `/orchestrator` workflow)
### 2. Define the Problem
Clearly articulate the **System Context** and **Problem Statement**.
* **Symptom**: What is the observable behavior? How does it differ from expected behavior?
* **Scope**: Which components are involved?
### 3. Formulate Hypotheses
List distinct, testable hypotheses.
* Avoid vague guesses.
* Differentiate between layers (e.g., "Frontend Hypothesis" vs "Backend Hypothesis").
* Example: "Race condition in UI state update" vs "Database schema misconfiguration".
### 4. Design Validation Tasks
For each hypothesis, design a specific validation task.
* **Objective**: What are you trying to prove or disprove?
* **Steps**: Precise, reproducible actions.
* **Code Pattern**: Provide the exact code or command to run (e.g., a specific SQL query, a Python script using the client library, a `curl` command).
* **Success Criteria**: Explicitly state what output confirms the hypothesis.
### 5. Execute and Document
Run the tasks in order. For each task, record:
* **Status**: ✅ VALIDATED, ❌ FAILED, or ⚠️ INCONCLUSIVE.
* **Findings**: Key observations and raw evidence (logs, screenshots).
* **Conclusion**: Does this support or refute the hypothesis?
### 6. Determine Root Cause
Synthesize the findings into a **Root Cause Analysis**.
* Identify the Primary Root Cause.
* Assign a Confidence Level.
* Propose specific fixes.
## Best Practices
* **Be Specific**: Don't just say "check the logs." Say "grep for 'Error 500' in `/var/log/nginx/access.log`".
* **Isolate Variables**: Change one thing at a time.
* **Validate Assumptions**: Verify configuration and versions first (e.g., "Task 1: Validate Current Schema").
* **Preserve Evidence**: Keep the specific trace IDs, log timestamps, or reproduction scripts.
## Language-Specific Modules
The `languages/` directory contains **modular, language-specific debugging guides**. When debugging a project, load the relevant language module to augment this protocol with language-specific tools, hypothesis categories, and validation strategies.
**Convention:** Each module is a standalone markdown file at `languages/{language}.md`.
**How to use:**
1. Identify the primary language of the codebase being debugged
2. Load the corresponding module from `languages/`
3. Integrate its toolchain, hypothesis categories, and validation tasks into your debugging session
**Available modules:**
| Module | Languages/Runtimes |
|---|---|
| [Rust](languages/rust.md) | Rust (cargo, rustc, tokio) |
| [Frontend](languages/frontend.md) | Vue 3, React, browser, Vite |
> **Contributing new modules:** To add support for a new language, create `languages/{language}.md` following the structure of existing modules. Each module should include: a toolchain reference table, language-specific hypothesis categories, validation task patterns, and an error-type-to-first-action quick reference.
## Rule Compliance
When debugging, verify against:
- Error Handling Principles @error-handling-principles.md (proper error propagation)
- Logging and Observability Principles @logging-and-observability-principles.md (structured logging for diagnostics)
- Testing Strategy @testing-strategy.md (regression test for the fix)
@@ -0,0 +1,82 @@
# Debugging Session: [Issue Name]
## SYSTEM CONTEXT
[Describe the system components involved, relevant technologies, and the general environment.]
## PROBLEM STATEMENT
**Symptom**:
[Describe the observable issue. What is happening vs what should happen?]
**Hypotheses**:
1. **[Layer/Component] Hypothesis**: [Description of potential cause]
2. **[Layer/Component] Hypothesis**: [Description of potential cause]
## VALIDATION TASKS
### Task 1: [Task Name]
**Objective**: [What specific fact or behavior are we verifying?]
**Steps**:
1. [Step 1]
2. [Step 2]
3. ...
**Required Code Pattern/Command**:
```[language]
[Code or command to execute]
```
**Success Criteria**: [What specific output confirms or denies the hypothesis?]
---
### Task 2: [Task Name]
**Objective**: ...
...
## OUTPUT FORMAT (for each task)
```markdown
### Task N: [Task Name]
**Status**: ✅ VALIDATED / ❌ FAILED / ⚠️ INCONCLUSIVE
**Findings**:
- [Observation 1]
- [Observation 2]
**Evidence**:
[Logs, screenshots, output]
**Conclusion**: [Impact on hypotheses]
```
## FINAL ROOT CAUSE DETERMINATION
```markdown
## ROOT CAUSE ANALYSIS SUMMARY
**Primary Root Cause**: [Component/Reason]
**Confidence Level**: [High / Medium / Low]
**Supporting Evidence**:
1. [Evidence 1]
2. [Evidence 2]
**Recommended Fix Priority**:
1. [Fix A]
2. [Fix B]
**Risks if Wrong**:
- [Risk 1]
```
## EXECUTION GUIDELINES
- Execute tasks in order.
- Document all commands and outputs.
- Adapt the plan if new information invalidates later tasks.
@@ -0,0 +1,342 @@
# Frontend Debugging Module
Language-specific debugging guide for frontend projects (Vue 3, React, browser-based SPAs). Use alongside the main [Debugging Protocol](../SKILL.md).
---
## Toolchain Reference
| Tool | Purpose | When to Use |
|---|---|---|
| Chrome DevTools — **Console** | Runtime errors, warnings, `console.debug` traces | **Always first** — fastest signal |
| Chrome DevTools — **Network** | Request waterfall, response status, payload shape | Data not rendering, 401/403/500, CORS |
| Chrome DevTools — **Elements** | Computed styles, DOM structure, CSS specificity | Styling / layout issues |
| Chrome DevTools — **Performance** | Frame rate, scripting time, rendering phases | Jank, slow interactions |
| Chrome DevTools — **Application** | localStorage, sessionStorage, cookies, service workers | Auth state, cached flags, stale SW |
| Vue DevTools (browser extension) | Component tree, Pinia state, router history, event timeline | Vue-specific state/lifecycle issues |
| `router.afterEach` / `beforeEach` logging | Navigation guard execution trace | Routing bugs, blank screens, redirect loops |
| `onMounted` / `useEffect` logging | Component lifecycle confirmation | Component "never mounts" symptoms |
| `vite dev --force` | Force-clear Vite module cache | Stale build artifacts after dependency changes |
| `npx vue-tsc -b` | Full template type checking | Type errors that only surface at build time |
---
## Phase 1: Initialize Session — Frontend Context
Add these fields to the debugging session document:
```markdown
### Frontend Context
- **Framework version:** (e.g. Vue 3.5.x, React 19.x)
- **Bundler:** (e.g. Vite 7.x)
- **CSS framework:** (e.g. Tailwind v4, none)
- **State management:** (e.g. Pinia 3.x, Redux, Zustand)
- **Router:** (e.g. Vue Router 5.x, React Router 7.x)
- **Browser:** (exact version — behavior varies across engines)
- **PWA/Service Worker:** yes / no (stale caches are a common red herring)
- **Auth provider:** (e.g. Supabase Auth, Firebase Auth, custom JWT)
```
---
## Phase 3: Hypothesis Categories
Common frontend-specific hypothesis categories:
| Category | Example Hypotheses |
|---|---|
| **Routing & Navigation** | Guard blocks route; redirect loop between auth/onboarding; `router.replace()` resolves but component never mounts; stale `meta` on route definition |
| **State Management** | Pinia store holds stale data after auth change; double-fetch from co-mounted components; reactive loss from `const { x } = store` without `storeToRefs`; store initialized before auth resolves |
| **CSS × Animation** | `transitionend` event never fires due to `@layer` cascade; `mode="out-in"` blocks entering component indefinitely; animation stuck in leaving state; blank screen with zero console errors |
| **Data & Rendering** | Backend payload shape mismatch (missing field → `undefined` → silent `v-if` failure); wrong reactive access pattern (`v-if="fn()"` instead of `computed`); component renders before async data resolves |
| **Auth & Session** | Token expired but UI doesn't redirect; `localStorage` holds stale session after logout; race between auth state resolution and guard evaluation; middleware applies to wrong routes |
---
## Phase 4: Validation Task Patterns
### Routing & Navigation — Blank Screen After Navigation
**Symptom:** User navigates, router reports success, destination component never renders. Page is blank with zero console errors.
**Validation steps:**
1. **Confirm no component mount** — add lifecycle logging to the destination:
```javascript
// Vue 3
onMounted(() => console.debug('[TargetView] onMounted fired'));
// React
useEffect(() => console.debug('[TargetView] mounted'), []);
```
If the mount log never fires but the router reports navigation complete → this is NOT a routing logic bug.
2. **Trace the guard chain** — add diagnostic logging:
```typescript
router.beforeEach((to, from) => {
console.debug(`[Router] beforeEach: ${from.path} → ${to.path}`);
});
router.afterEach((to) => {
console.debug(`[Router] afterEach: arrived at ${to.path}`);
});
```
If `afterEach` fires but `onMounted` does not → suspect the **transition mechanism** (see CSS × Animation below).
3. **Check for redirect loops** — watch the URL bar or router logs for oscillating paths (e.g. `/login` → `/home` → `/onboarding` → `/login`).
4. **Verify auth state at guard time** — log the auth store's state inside each guard to confirm it's resolved (not still loading).
---
### State Management — Stale or Missing Data
**Symptom:** Component renders but shows stale data, or renders empty despite successful API call.
**Validation steps:**
1. **Inspect Pinia state via Vue DevTools** — check the store's current values. Is the data present?
2. **Check for destructuring reactivity loss:**
```typescript
// ❌ Loses reactivity — x is a plain value snapshot
const { tasks } = useTaskStore();
// ✅ Preserves reactivity
const { tasks } = storeToRefs(useTaskStore());
```
3. **Check for race conditions with auth:**
```typescript
// The store action fires before auth resolves → API returns 401 → store stays empty
// Fix: await auth resolution before first store fetch
```
4. **Check for duplicate fetches from co-mounted components:**
- Open Network tab → filter by XHR → navigate to the page
- If two identical requests appear → the store needs a loading-state guard
---
### CSS × Animation — Transition Stuck (Blank Screen)
> **This section subsumes the former `css-transition-pitfalls` skill.**
**Symptom:** Component "never mounts" after a route change. Blank screen with zero console errors. Works on hard refresh but recurs on SPA navigation. `router.afterEach` fires but `onMounted` does not.
#### The Anti-Pattern
```
User clicks link → Router navigates → Old component should leave → New component should enter
↓
CSS framework's @layer rules
override transition properties
↓
transitionend event NEVER fires
↓
SPA framework waits forever
↓
New component NEVER mounts
↓
BLANK SCREEN (no errors)
```
#### Why It's Hard to Catch
1. **No errors** — everything completes from the router's perspective
2. **Works on refresh** — hard refresh bypasses the SPA transition entirely
3. **Looks like a logic bug** — the natural instinct is to debug auth state, guards, or template conditionals
4. **Intermittent** — may only trigger on specific route pairs where the leaving component has conflicting animations
#### Affected SPA Frameworks
| SPA Framework | Transition Mechanism | Vulnerable Mode |
|---|---|---|
| **Vue 3** | `<Transition mode="out-in">` | `mode="out-in"` blocks enter until leave completes |
| **React** (Framer Motion) | `AnimatePresence mode="wait"` | `mode="wait"` blocks enter until exit completes |
| **React** (React Transition Group) | `SwitchTransition mode="out-in"` | Same pattern |
| **Svelte** | `transition:` directive with `|local` | Less common but possible |
#### CSS Frameworks That Can Cause This
| CSS Framework | Why |
|---|---|
| **Tailwind v4** | Uses `@layer theme, base, components, utilities` — unlayered styles (like Vue's transition classes) can be unexpectedly deprioritized |
| **Open Props** | Uses `@layer` for custom property organization |
| **Any framework using `@layer`** | The CSS Cascade spec gives layered styles lower priority than unlayered, BUT framework bundlers may re-layer your styles |
#### Validation Steps
1. **Confirm the symptom class** — add `onMounted` logging to the destination component. If mount log never fires but `afterEach` does → transition stuck.
2. **Rule out logic issues (time-box: 15 minutes):**
- Navigation guard allows the route (check `afterEach` logging)
- Auth state is correct at navigation time
- No redirect loops
3. **Inspect the transition mechanism** — check your root layout for sequential transition modes:
```html
<!-- Vue: look for mode="out-in" -->
<Transition name="fade" mode="out-in">
<!-- React Framer Motion: look for mode="wait" -->
<AnimatePresence mode="wait">
<!-- React Transition Group: look for mode="out-in" -->
<SwitchTransition mode="out-in">
```
If found → this is almost certainly the cause.
4. **Verify CSS layer interference:**
```bash
# Check for @layer in built CSS
grep -r "@layer" node_modules/tailwindcss/ | head -5
grep -r "@import.*tailwindcss" src/
# In browser DevTools: inspect the leaving element during transition
# Look for: computed opacity, computed transition property
```
5. **Apply the fix — remove sequential transition mode:**
```html
<!-- BEFORE (broken): sequential, depends on transitionend -->
<Transition name="fade" mode="out-in">
<component :is="Component" />
</Transition>
<!-- AFTER (fixed): simultaneous, always mounts new component -->
<Transition name="fade">
<component :is="Component" :key="$route.path" />
</Transition>
```
Update CSS to handle simultaneous leave/enter:
```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;
}
/* Parent needs position:relative to contain the absolute element */
#main-content {
position: relative;
}
```
> **Why `!important`?** CSS `@layer` cascade can override your transition classes. `!important` guarantees your transition properties apply regardless of layer ordering.
#### Key Insight
> **When a component doesn't mount after navigation: suspect the transition mechanism FIRST, not the routing logic.** The router will report success even when the component never renders. This is counter-intuitive because the blank screen looks like a routing/auth problem, but it's actually a CSS/animation problem.
---
### Data & Rendering — Silent Template Failures
**Symptom:** Page renders but expected content is missing. No console errors. Data is present in the store.
**Validation steps:**
1. **Check for `v-if` on optional properties:**
```html
<!-- Silent failure if exercise.options is undefined — v-if produces no error -->
<div v-if="exercise.options.length > 0">
<!-- Fix: optional chaining -->
<div v-if="exercise.options?.length">
```
2. **Check for plain function calls in templates:**
```html
<!-- ❌ Re-evaluates on every render cycle; may return inconsistent results -->
<div v-if="isVisible()">
<!-- ✅ Cached, reactive -->
<div v-if="isVisibleComputed">
```
3. **Check for backend payload mismatches** — log the raw API response and compare field names/types against what the component expects. Common: `snake_case` from backend vs `camelCase` in frontend.
4. **Check for async rendering issues:**
```html
<!-- Component renders before data arrives — shows empty state permanently -->
<TaskCard :task="task" />
<!-- Fix: guard with v-if -->
<TaskCard v-if="task" :task="task" />
```
---
### Auth & Session — Redirect Loops and Ghost Sessions
**Symptom:** User gets stuck in a redirect loop between login, onboarding, and home pages. Or: user appears logged out despite having valid credentials.
**Validation steps:**
1. **Check localStorage for stale auth tokens:**
```javascript
// In console
Object.keys(localStorage).filter(k => k.includes('auth') || k.includes('supabase') || k.includes('token'));
```
2. **Verify auth state is resolved before guards run:**
```typescript
router.beforeEach(async (to) => {
console.debug('[Guard] authStore.isAuthenticated:', authStore.isAuthenticated);
console.debug('[Guard] authStore.isLoading:', authStore.isLoading);
// If isLoading is true here, the guard is racing the auth resolution
});
```
3. **Check for circular guard logic:**
- Guard A: "if not authenticated → redirect to /login"
- Guard B: "if authenticated and not onboarded → redirect to /onboarding"
- Guard C: "if on /onboarding and already onboarded → redirect to /home"
- If the onboarding flag is stale or the database read is async → loop
4. **Test by clearing all storage and hard-refreshing:**
```javascript
localStorage.clear(); sessionStorage.clear(); location.reload();
```
If the issue resolves → the problem is stale cached state, not logic.
---
## Phase 6: Confidence Adjustments
| Evidence | Confidence Impact |
|---|---|
| `onMounted` log never fires but `afterEach` does | **High** — definitively a transition/rendering issue, not routing |
| DevTools Network shows no request fired | **High** — data never fetched (guard blocked, or fetch never called) |
| Vue DevTools shows correct Pinia state but UI is empty | **High** — template rendering bug (wrong `v-if`, missing reactivity) |
| Issue resolves on hard refresh but recurs on SPA nav | **High** — transition-stuck, service worker cache, or stale SPA state |
| `@layer` found in CSS framework and `mode="out-in"` is used | **Definitive** for transition-stuck diagnosis |
| Issue only appears on specific route pairs | **Medium-High** — likely CSS/animation interaction specific to the leaving component |
| Console shows no errors at all | **Medium** — rules out JS exceptions; narrows to CSS/template/state issues |
---
## Quick Reference: Symptom → First Action
| Symptom | First Action |
|---|---|
| Blank screen after navigation (no errors) | Add `onMounted` logging → if never fires, check `<Transition mode>` |
| Blank screen after navigation (console errors) | Read the error — usually a runtime exception in the entering component |
| Redirect loop (URL oscillates) | Log all guards with `beforeEach` → trace the loop pattern |
| Data present in store but not rendered | Check `v-if` conditions for undefined/null access; check for destructuring without `storeToRefs` |
| Component renders then immediately disappears | Check for a `watch` or `watchEffect` that re-triggers a navigation or state reset |
| API call fires twice | Check for co-mounted components both calling the same store action; add loading-state guard |
| Works locally but not in production | Check service worker cache; run `vite build` locally to reproduce; check env variables |
| Works on refresh but fails on SPA navigation | Suspect transition mechanism or stale component state from previous route |
| Auth state incorrect in guard | Log auth store state inside the guard; check if auth initialization is awaited before router starts |
| Styles not applying | Check CSS specificity vs `@layer` cascade; inspect computed styles in DevTools Elements tab |
---
## Related Principles
- Vue Idioms and Patterns @vue-idioms-and-patterns.md
- TypeScript Idioms and Patterns @typescript-idioms-and-patterns.md
- Error Handling Principles @error-handling-principles.md
- Logging and Observability Principles @logging-and-observability-principles.md
- Testing Strategy @testing-strategy.md
@@ -0,0 +1,136 @@
# Rust Debugging Module
Language-specific debugging guide for Rust projects. Use alongside the main [Debugging Protocol](../SKILL.md).
---
## Toolchain Reference
| Tool | Purpose | When to Use |
| --------------------- | ----------------------------------------- | --------------------------------------------------- |
| `cargo check` | Fast type/borrow checking (no codegen) | **Always first** — 2-5× faster than `cargo build` |
| `cargo clippy` | Lint for idiomatic issues and subtle bugs | After `cargo check` passes — catches logical errors |
| `cargo test` | Run test suite | Validate fixes, reproduce failures |
| `cargo expand` | Expand macros to see generated code | When a macro-generated error is unclear |
| `cargo tree` | Visualize dependency graph | Dependency conflicts, version mismatches |
| `cargo audit` | Check for known vulnerabilities | Security-related investigations |
| `RUST_BACKTRACE=1` | Full backtrace on panic | Runtime panics — always enable first |
| `RUST_BACKTRACE=full` | Backtrace with line numbers | When `=1` doesn't give enough detail |
| `miri` | Detect undefined behavior in unsafe code | Suspected UB, memory safety issues |
---
## Phase 1: Initialize Session — Rust Context
Add these fields to the debugging session document:
```markdown
### Rust Context
- **Rust toolchain version:** (output of `rustc --version`)
- **Cargo.toml key dependencies:** (list with versions)
- **Build profile:** debug / release
- **Target:** default / cross-compile target
```
---
## Phase 3: Hypothesis Categories
Common Rust-specific hypothesis categories:
| Category | Example Hypotheses |
| ------------------- | -------------------------------------------------------------------------- |
| **Borrow Checker** | "Function borrows `self` mutably while an immutable borrow is still live" |
| **Lifetime** | "Returned reference outlives the data it points to" |
| **Async/Runtime** | "Blocking call inside async context starves the Tokio runtime" |
| **Type Mismatch** | "Trait bound not satisfied due to missing `Send`/`Sync` on spawned future" |
| **FFI/Unsafe** | "Raw pointer from C FFI is null but not checked before dereference" |
| **Macro Expansion** | "Procedural macro generates code that conflicts with local type names" |
| **Dependency** | "Two crates depend on different major versions of the same crate" |
---
## Phase 4: Validation Task Patterns
### Borrow Checker Errors — Reading Strategy
When the compiler emits a borrow error:
1. **Read the error bottom-up** — the last line shows *where* the conflict is, not *why*
2. **Find the two conflicting borrows** — the error always names them
3. **Check the lifetime of each borrow** — does one live longer than expected?
4. **Common fixes:**
- Clone the data (only if cheap — document with `// CLONE:`)
- Restructure to avoid overlapping borrows (split struct into components)
- Use `Cell`/`RefCell` for interior mutability (single-threaded only)
- Use scoping braces `{ }` to limit borrow lifetime
### Async Debugging
```bash
# Enable Tokio's runtime tracing
RUSTFLAGS="--cfg tokio_unstable" cargo build
# Then use tokio-console for live runtime inspection
tokio-console
```
Validation steps for async issues:
1. Check for `spawn_blocking` around any blocking I/O
2. Verify all `.await` points — are any holding locks across them?
3. Look for `select!` branches that aren't cancellation-safe
4. Run with `tokio::time::pause()` in tests for deterministic timing
### Macro Debugging
```bash
# Expand all macros in a specific file
cargo expand --lib path::to::module
# Expand only derive macros
cargo expand --lib path::to::module 2>&1 | grep -A 50 "impl"
```
### Unsafe / UB Detection
```bash
# Run tests under Miri (detects UB, use-after-free, unaligned access)
cargo +nightly miri test
# Common Miri flags
MIRIFLAGS="-Zmiri-backtrace=full" cargo +nightly miri test
```
---
## Phase 6: Confidence Adjustments
| Evidence | Confidence Impact |
| ---------------------------------------------- | -------------------------------------------- |
| `cargo check` error points exactly at the line | High — compiler errors are precise |
| `cargo clippy` lint matches the symptom | Medium-High — Clippy catches many real bugs |
| `miri` reports UB | **Definitive** for unsafe code paths |
| `cargo expand` shows unexpected generated code | High — confirms macro-related issues |
| "works in debug, fails in release" pattern | Likely UB or optimization-sensitive ordering |
---
## Quick Reference: Error Code → First Action
| Error Type | First Action |
| ----------------------------- | ------------------------------------------------------------ |
| `E0382` (use of moved value) | Check if value can be borrowed instead of moved |
| `E0502` (conflicting borrows) | Identify the two borrows, scope them apart |
| `E0106` (missing lifetime) | Add lifetime parameter, check if owned type is better |
| `E0277` (trait not satisfied) | Check `Send`/`Sync` bounds, look for non-Send types in async |
| `E0308` (type mismatch) | Check `From`/`Into` impls, verify generic constraints |
| Runtime panic | `RUST_BACKTRACE=1 cargo test` — read the backtrace |
| Segfault / UB | `cargo +nightly miri test` — Miri will find it |
| Macro error | `cargo expand` the module — read the generated code |
---
## Related Principles
- Rust Idioms and Patterns @rust-idioms-and-patterns.md
- Error Handling Principles @error-handling-principles.md
- Concurrency and Threading Principles @concurrency-and-threading-principles.md
- Testing Strategy @testing-strategy.md
+50
View File
@@ -0,0 +1,50 @@
---
name: frontend-design
description: Generates distinctive, production-grade frontend interfaces and artifacts (React, Vue, HTML/CSS). Prioritizes bold aesthetics, unique typography, and motion to avoid generic designs. Use when building websites, landing pages, dashboards, posters, or when the user requests to style, beautify, or create visually striking UI.
---
This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
## Design Thinking
Before coding, understand the context and commit to a BOLD aesthetic direction:
- **Purpose**: What problem does this interface solve? Who uses it?
- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
- **Constraints**: Technical requirements (framework, performance, accessibility).
- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
- Production-grade and functional
- Visually striking and memorable
- Cohesive with a clear aesthetic point-of-view
- Meticulously refined in every detail
## Frontend Aesthetics Guidelines
Focus on:
- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.
NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.
Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.
9. All implementations must also pass through the agent's standard rule checks — this skill enhances visual output, but does not override engineering rules.
## Rule Compliance
Before implementing, verify against:
- Project Structure @project-structure.md (component organization)
- Testing Strategy @testing-strategy.md (component tests)
- Security Principles @security-principles.md (XSS prevention)
- Accessibility Principles @accessibility-principles.md (WCAG compliance)
**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
Remember: You are capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
+90
View File
@@ -0,0 +1,90 @@
---
name: guardrails
description: Pre-flight checklist and post-implementation self-review protocol. Use before generating any code (pre-flight) and after writing code but before verification (self-review) to catch issues early.
---
# Agent Guardrails Skill
## Purpose
Structured checklists that ensure the agent considers all applicable rules before and after writing code. Catches issues that would otherwise only surface during verification.
## When to Invoke
- **Pre-Flight:** At the start of Phase 2 (Implement), before writing any code
- **Self-Review:** At the end of Phase 2 (Implement), after writing code but before Phase 3/4
---
## Pre-Flight Checklist
Run through this checklist **before writing any code**:
- [ ] Identified all applicable rules from `.agents/rules/`
- [ ] Searched for existing patterns in codebase (Pattern Discovery Protocol from `architectural-pattern.md`)
- [ ] Confirmed project structure alignment (`project-structure.md`)
- [ ] Identified I/O boundaries that need abstraction
- [ ] Determined test strategy (unit/integration/E2E)
- [ ] Reviewed `rule-priority.md` for any potential conflicts
If any item cannot be checked, **stop and resolve** before proceeding.
---
## Post-Implementation Self-Review
Run through this checklist **after writing code, before verification**:
### Security
- [ ] No hardcoded secrets or configuration values
- [ ] All user input validated at system boundaries
- [ ] Parameterized queries (no string concatenation for SQL)
### Testability
- [ ] All I/O operations behind interfaces/abstractions
- [ ] Business logic is pure (no side effects in calculations)
- [ ] Dependencies injected, not hardcoded
### Observability
- [ ] All public operation entry points logged (start/success/failure)
- [ ] Structured logging with correlation IDs
- [ ] Appropriate log levels (not everything is INFO)
### Error Handling
- [ ] Error paths handled explicitly (no empty catch blocks)
- [ ] Errors provide context (wrapped with additional info)
- [ ] Resources cleaned up in error paths (defer/finally)
### Testing
- [ ] Tests cover happy path
- [ ] Tests cover at least 2 error paths
- [ ] Tests cover edge cases relevant to the domain
- [ ] If I/O adapters were modified: integration tests exist and pass
- [ ] If UI was modified: E2E tests exist (or Phase 3.5 is planned)
### Consistency
- [ ] Follows existing codebase patterns (>80% consistency)
- [ ] Naming conventions match the codebase
- [ ] File organization matches `project-structure.md`
---
## Language-Specific Self-Review
After completing the universal checklist above, load the relevant language-specific checklist:
| Language | Checklist |
|---|---|
| **Go** | `languages/go.md` |
| **TypeScript** | `languages/typescript.md` *(placeholder — create when needed)* |
| **Flutter/Dart** | `languages/flutter.md` *(placeholder — create when needed)* |
| **Rust** | `languages/rust.md` *(placeholder — create when needed)* |
> Only load the file for languages you are actively writing. If the file doesn't exist yet, skip — but flag its absence so it can be created.
---
## Rule Compliance
This skill enforces:
- All mandates (always-on rules)
- Architectural Patterns @architectural-pattern.md
- Testing Strategy @testing-strategy.md
- Rule Priority @rule-priority.md
+71
View File
@@ -0,0 +1,71 @@
# Go Self-Review Checklist
> **Load this file** after completing the universal self-review in `SKILL.md`, when the code under review is Go.
---
## Resource Cleanup
- [ ] **No bare `defer X.Close()`** — all deferred closers use error-checked closures with `slog`
- [ ] **No `//nolint:errcheck`** directives anywhere in new code
- [ ] **Transaction rollbacks** check for `sql.ErrTxDone` before logging
- [ ] **HTTP response bodies** are drained (`io.Copy(io.Discard, ...)`) before closing
### Correct Patterns
```go
// ✅ Row/statement/connection Close
defer func() {
if err := rows.Close(); err != nil {
slog.Warn("failed to close rows", "error", err, "operation", "<operation>")
}
}()
// ✅ Transaction Rollback
defer func() {
if err := tx.Rollback(); err != nil && !errors.Is(err, sql.ErrTxDone) {
slog.Error("failed to rollback transaction", "error", err, "operation", "<operation>")
}
}()
// ✅ HTTP Response Body
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)
}
}()
```
---
## Logging
- [ ] **No `fmt.Printf` / `fmt.Println` / `log.Printf`** — only `slog` in production code
- [ ] All log calls use **structured key-value pairs** (no `fmt.Sprintf` inside log messages)
- [ ] Error logs include `"error", err` as a key-value pair
---
## Error Handling
- [ ] All errors are wrapped with context using `%w`: `fmt.Errorf("doing X: %w", err)`
- [ ] No duplicate wrapping of the same error
- [ ] Sentinel errors used for expected conditions (`ErrNotFound`, `ErrUnauthorized`)
---
## Static Analysis
- [ ] `go vet ./...` passes with zero issues
- [ ] No new `//nolint:` directives without a `// NOLINT:` rationale comment
- [ ] `//nolint:errcheck` specifically is **never** used — handle the error instead
---
## References
- Go Idioms and Patterns @go-idioms-and-patterns.md (§ Idiomatic Patterns, § Formatting and Static Analysis)
- Logging and Observability Principles @logging-and-observability-principles.md (§ Go/slog)
- Error Handling Principles @error-handling-principles.md (§ Resource Cleanup)
+93
View File
@@ -0,0 +1,93 @@
---
name: mobile-design
description: Generates distinctive, production-grade mobile interfaces for Flutter and React Native. Prioritizes platform-native patterns, adaptive layouts, and fluid motion. Use when building mobile apps, screens, widgets, or when the user requests to style or create visually striking mobile UI.
---
# Mobile Design Skill
This skill guides creation of distinctive, production-grade mobile interfaces that feel native to the platform while maintaining a unique aesthetic identity. Implement real working code with exceptional attention to platform conventions and creative design choices.
The user provides mobile UI requirements: a screen, widget, feature, or complete app interface. They may include context about the platform (iOS/Android), audience, or technical constraints.
## Design Thinking
Before coding, understand the context and commit to a platform-aware aesthetic direction:
- **Platform**: iOS (Cupertino), Android (Material 3), or cross-platform? Each has distinct expectations.
- **Tone**: Pick a direction — refined luxury, playful/energetic, utilitarian/efficient, editorial/content-first, organic/warm, bold/geometric, soft/pastel, etc.
- **Constraints**: Performance on low-end devices, offline capability, accessibility.
- **Differentiation**: What makes this app memorable? What's the signature interaction?
**CRITICAL**: Mobile users have muscle memory. Respect platform conventions (navigation patterns, gesture zones, system UI) while injecting personality through typography, color, motion, and layout composition.
## Mobile Design Guidelines
### Platform Conventions
- **iOS**: Bottom tab bar, large title navigation, swipe-to-go-back, Cupertino widgets
- **Android**: Material 3, navigation rail/drawer, FAB, top app bar
- **Cross-platform**: Use adaptive widgets that render platform-appropriate UI
### Typography
- Choose distinctive but highly readable fonts at mobile sizes (16px+ body)
- Respect dynamic type / font scaling — never hardcode font sizes
- Use `TextTheme` in Flutter, keep a consistent type scale
- Pair a characterful display font with a supremely legible body font
### Color & Theme
- Support **both light and dark themes** — always
- Use `ColorScheme` / `ThemeData` for consistency
- Ensure contrast ratios work on OLED (pure black) and LCD displays
- Use color purposefully — primary actions, status indicators, brand identity
### Motion & Animation
- Use **meaningful motion** — animations should communicate state changes, not decorate
- Keep animations under 300ms for interactions, up to 500ms for transitions
- Use `Hero` animations for shared element transitions
- Stagger list item animations for visual polish
- Respect `reduceMotion` / accessibility settings — always provide a static fallback
### Layout & Spacing
- Design for **one-handed use** — critical actions in thumb-reach zone (bottom 60% of screen)
- Use safe area insets — never overlap system UI (notch, home indicator, status bar)
- Design for varying screen sizes — phones, tablets, foldables
- Use responsive breakpoints: compact (<600dp), medium (600-840dp), expanded (>840dp)
- Generous touch targets: minimum 48x48dp
### Performance
- Avoid rebuilding expensive widget trees — use `const` constructors
- Lazy-load lists with `ListView.builder` (never `ListView` for large lists)
- Optimize images — use `cached_network_image`, proper resolution variants
- Profile on real devices, not just emulators
## Flutter-Specific Patterns
```dart
// ✅ Good: Adaptive widget that respects platform
Widget buildButton(BuildContext context) {
return Platform.isIOS
? CupertinoButton(child: text, onPressed: onTap)
: ElevatedButton(child: text, onPressed: onTap);
}
// ✅ Good: Responsive layout with breakpoints
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
if (width >= 840) return _expandedLayout();
if (width >= 600) return _mediumLayout();
return _compactLayout();
}
// ❌ Bad: Hardcoded sizes that break on different devices
Container(width: 375, height: 812) // iPhone X only!
```
## Rule Compliance
Before implementing, verify against:
- Project Structure @project-structure.md (Flutter/Mobile layout, feature organization)
- Testing Strategy @testing-strategy.md (widget tests, integration tests)
- Security Principles @security-principles.md (secure storage, API key handling)
- Accessibility Principles @accessibility-principles.md (screen reader, dynamic type, contrast)
- Architectural Patterns @architectural-pattern.md (Riverpod 3 Notifier, repository pattern)
**IMPORTANT**: Mobile has unique constraints — battery life, network variability, and user context (one-handed, on-the-go). Every design choice should respect these realities. Beautiful UI that drains battery or stutters on scroll is a failure.
Remember: The best mobile apps feel like they were designed specifically for the device in your hand. Make every pixel and every interaction intentional.
+227
View File
@@ -0,0 +1,227 @@
---
name: perf-optimization
description: >
Profile-driven performance optimization protocol. Use when profiling data (CPU, heap, trace)
is available or when the user requests performance analysis. Covers methodology, pattern catalog,
safety invariants, and when-to-stop heuristics. Language-specific tooling is in languages/*.md.
---
# Performance Optimization Skill
## When to Use
- User provides profiling data (pprof, flamegraph, py-spy, Chrome DevTools, Dart DevTools)
- User asks to analyze or optimize performance of a specific component
- A benchmark regression is detected
- After deploying a new feature that touches a hot path
## Core Methodology
```mermaid
graph LR
P1[Profile] --> P2[Analyze]
P2 --> P3[Prioritize]
P3 --> P4[Optimize]
P4 --> P5[Benchmark]
P5 --> P6{Improvement?}
P6 -->|Yes| P7[Verify & Ship]
P6 -->|No| P3
```
### Step 1: Profile
Collect profiling data using the language-appropriate tool. Load the relevant `languages/*.md` module for exact commands.
**Output:** Raw profiling data (CPU profile, heap profile, or trace).
### Step 2: Analyze
Read the profile. Focus on these principles (universal across all runtimes):
1. **Focus on `cum` (cumulative):** The total resources consumed by a function AND everything it called. This finds the expensive architectural flows.
2. **Contextualize `flat`:** Resources consumed by the function itself only. If a runtime function (GC, malloc, syscall) has high flat time, trace it UP the call chain to find the user-land code that triggered it.
3. **Ignore runtime noise:** Scheduler overhead (`runtime.mcall`, `runtime.systemstack`, GC workers) will always appear. Note if GC pressure is high, but don't try to "fix" the scheduler.
4. **Separate benchmark artifacts from production cost:** Test harness allocations (e.g., `httptest.NewRequest`, `ResponseRecorder`) inflate heap profiles but don't exist in production.
**Output:** Structured analysis document in `docs/research_logs/{component}-perf-analysis.md`.
### Step 3: Prioritize
Rank fixes by **impact/risk ratio**:
| Priority | Criteria |
|---|---|
| Do first | Low risk, high impact (caching, pre-allocation, fast-reject) |
| Do second | Medium risk, high impact (library swap, algorithm change) |
| Do last | High risk, high impact (major refactor, custom implementation) |
| Skip | Any risk, low impact (micro-optimization below noise floor) |
**Rule:** If a fix requires more than 1 day AND saves < 20% on the hot path, defer it.
### Step 4: Optimize
Implement one fix at a time. For each fix:
1. Write tests FIRST (TDD — Red → Green → Refactor)
2. Implement the fix
3. Run all existing tests to verify no regression
4. Benchmark immediately
**Never batch multiple optimizations into one commit.** Each fix must be independently verifiable and revertable.
### Step 5: Benchmark
Compare before/after with the **exact same benchmark configuration** (same `-benchtime`, same `-count`, same machine load). Report:
- `ns/op` (latency)
- `B/op` (memory per operation)
- `allocs/op` (heap allocations per operation)
### Step 6: When to Stop
**Stop optimizing when any of these are true:**
- Remaining CPU is in hardware-optimized assembly (AES-NI, P-256, SIMD) — you cannot beat the hardware
- Remaining allocations are from the language runtime itself (GC, goroutine stacks, HTTP server internals)
- The fix requires a custom implementation of a well-audited library — the security/maintenance risk outweighs the perf gain
- The measured improvement is < 5% and within benchmark noise
---
## Optimization Pattern Catalog
These are generic, language-agnostic patterns. Apply them when the profiling data shows the corresponding symptom.
### Pattern: Result Caching
**Symptom:** Same expensive computation repeated with identical inputs (crypto verification, JSON parsing, regex compilation).
**Fix:** Cache results keyed by input hash. Use bounded LRU with TTL to prevent memory exhaustion.
**Safety invariant:** When caching security-sensitive results (auth tokens, permission checks):
- ALWAYS re-validate expiry/revocation on cache hit
- ALWAYS bound cache size (DoS protection)
- ALWAYS set TTL shorter than the security credential's validity period
### Pattern: Pre-allocation
**Symptom:** High `allocs/op` from repeatedly constructing the same objects (option structs, config slices, header maps).
**Fix:** Build the object once at init time, share it read-only across requests. Safe for concurrent use if the object is immutable after construction.
### Pattern: Fast-Reject / Short-Circuit
**Symptom:** Expensive validation path runs even for clearly invalid inputs.
**Fix:** Add a cheap structural pre-check before the expensive path. Examples: check string length before regex, count delimiters before parsing, check content-type before deserialization.
### Pattern: Library Swap
**Symptom:** High allocation count or CPU in a third-party library's internal parsing/serialization.
**Fix:** Replace with a library that uses lower-allocation strategies (manual scanners vs `encoding/json.Decoder`, zero-copy parsing, arena allocation).
**Safety invariant:** When swapping security-critical libraries (JWT, TLS, crypto):
- Explicitly restrict accepted algorithms (prevent algorithm confusion attacks)
- Verify the replacement library is well-audited and actively maintained
- Run the full existing test suite — no behavioral change allowed
### Pattern: Pooling
**Symptom:** High GC pressure from many short-lived objects of the same type being allocated and discarded rapidly.
**Fix:** Use an object pool (sync.Pool in Go, object pool in Java, arena in Rust) to reuse allocations.
**Caveat:** Only effective when objects are uniform in size and have a clear acquire/release lifecycle. Misuse creates subtle bugs.
### Pattern: Batching
**Symptom:** Many small I/O operations (DB queries, HTTP calls, file writes) dominating wall-clock time.
**Fix:** Batch operations into fewer, larger calls. Examples: batch INSERT, pipeline Redis commands, buffer writes.
### Pattern: Artifact Partitioning by Change Frequency
**Symptom:** Deploying a small change invalidates a large cached artifact (JS bundle, Docker image, compiled binary), forcing consumers to re-download/rebuild the entire thing.
**Fix:** Partition build artifacts by change frequency so that stable layers survive volatile deploys:
- **Stable layer**: dependencies, vendor libraries, base images — changes rarely
- **Volatile layer**: application code — changes on every deploy
**Examples across stacks:**
- **JS/Bundler**: Vite `manualChunks` / Webpack `splitChunks` to isolate vendor libraries into separate chunks
- **Docker**: multi-stage builds with `COPY go.mod` + `RUN go mod download` BEFORE `COPY . .` — dependency layer caches across builds
- **Monorepo**: separate packages by change frequency so CI only rebuilds what changed
**Safety invariant:** Total artifact size stays the same or slightly increases (chunk overhead). The benefit is on **repeat consumption** — stable layers serve from cache.
**When NOT to apply:** One-shot artifacts with no caching benefit (single-use CI, ephemeral environments).
### Pattern: Dependency Discovery Parallelization
**Symptom:** Sequential resource discovery creates waterfalls — each resource is discovered only after the previous one completes (download → parse → discover next → download → ...).
**Fix:** Declare dependencies as early as possible so the system can fetch them in parallel:
- Move resource declarations upstream (earlier in the boot/parse sequence)
- Use explicit hints to bypass sequential discovery chains
**Examples across stacks:**
- **Browser**: `<link rel="preconnect">` to establish connections before CSS/JS requests them; move CSS `@import` to HTML `<link>` for parallel discovery
- **Go**: `go mod download` before build to prefetch modules
- **DB**: connection pool warm-up at startup instead of on first query
- **DNS**: `dns-prefetch` hints for domains the app will contact
**Safety invariant:** Only pre-declare resources you WILL use. Unused preconnects/prefetches waste resources (TCP connections, DNS queries, module downloads).
### Pattern: Concurrent-Fetch Dedup
**Symptom:** Network tab shows two identical API calls fired at the same time. Multiple UI components mount simultaneously and each independently calls the same fetch function.
**Fix:** Add a loading-state guard (semaphore) at the store/service layer:
```
async function fetchData() {
if (isLoading) return // ← drop duplicate in-flight request
isLoading = true
try { data = await api.getData() }
finally { isLoading = false }
}
```
**When to apply:** When the same data store is used by multiple co-mounted components (e.g., a navigation bar and a page view both calling `fetchProfile()` on mount).
**Caveat:** This is a simple semaphore, not request dedup. If the data needs refreshing after the in-flight call completes, the caller should retry. For advanced use cases, consider a proper request dedup cache (e.g., TanStack Query's `staleTime`).
---
## Anti-Patterns (Things NOT to Do)
1. **Don't optimize runtime internals.** If `runtime.mallocgc` or `runtime.gcBgMarkWorker` is high, fix the USER CODE that triggers allocations — don't try to tune the GC directly.
2. **Don't replace battle-tested crypto with custom implementations.** The performance ceiling of ECDSA/RSA is in the math. Accept it.
3. **Don't optimize based on gut feeling.** Always profile first. Premature optimization is the root of all evil.
4. **Don't combine multiple optimizations into one commit.** If a combined commit causes a regression, you can't isolate which fix is at fault.
5. **Don't disable security features for performance.** Algorithm restriction, input validation, and expiry checks are non-negotiable.
6. **Don't profile without a stable baseline.** Run benchmarks with fixed parameters (`-benchtime`, `-count`, same machine load). Without a reproducible baseline, before/after comparisons are meaningless noise.
---
## Language Modules
Load the relevant language module when working with a specific runtime:
| Module | Use when |
|---|---|
| [Go](languages/go.md) | Go services, APIs, CLI tools |
| [Rust](languages/rust.md) | Rust binaries, libraries |
| [Python](languages/python.md) | Python services, CLI, data pipelines |
| [Frontend](languages/frontend.md) | Web frontends (JS/TS bundle, rendering, network) |
> **Contributing:** After completing a perf optimization session, extract generalizable patterns
> from your `docs/research_logs/` findings into this catalog. Project-specific details stay in
> the research log; reusable patterns belong here.
## Profiling Scripts
Language-specific data extraction scripts live in `scripts/`:
| Script | Purpose |
|---|---|
| [go-pprof.sh](scripts/go-pprof.sh) | Extract Go pprof CPU/heap profiles into agent-readable markdown |
| [frontend-lighthouse.sh](scripts/frontend-lighthouse.sh) | Two modes: `lighthouse` (Core Web Vitals, needs Chrome) or `bundle` (Vite chunk analysis, always works) |
@@ -0,0 +1,347 @@
# Frontend Performance Profiling — Vue 3 + Vite + PWA
## Stack Context
| Layer | Tool | Version |
|---|---|---|
| Framework | Vue 3 (SFC + Composition API) | ^3.5 |
| State | Pinia | ^3.0 |
| Router | Vue Router | ^5.0 |
| Bundler | Vite | ^7.3 |
| CSS | Tailwind CSS (Vite plugin) | ^4.2 |
| PWA | vite-plugin-pwa (Workbox) | ^1.2 |
| Backend client | Supabase JS | ^2.99 |
| Build | vue-tsc + vite build | — |
| Tests | Vitest + jsdom | ^4.1 |
---
## Profiling Toolchain
### Build-Time Analysis
| Tool | Purpose | Command |
|---|---|---|
| Vite bundle visualizer | Chunk sizes, tree-shaking effectiveness | `npx vite-bundle-visualizer` (generates `stats.html`) |
| `vite build --report` | Build timing and chunk summary | `npx vite build 2>&1` |
| `source-map-explorer` | Trace bytes to source files | `npx source-map-explorer dist/assets/*.js` |
| `bundlephobia` | Check dependency size before adding | `npx bundlephobia <package-name>` |
**Quick bundle size check (no extra dependency needed):**
```bash
# Build and report chunk sizes
cd apps/frontend && npx vite build 2>&1 | grep -E "(dist/|\.js|\.css)" | sort -t'│' -k2 -rn
```
**Detailed analysis with rollup-plugin-visualizer:**
```bash
# Install temporarily and run
cd apps/frontend && npx vite-bundle-visualizer
# Opens stats.html in browser — shows treemap of every module
```
### Runtime Analysis (Chrome DevTools)
| Tab | What it measures | When to use |
|---|---|---|
| **Performance** | Frame rate, scripting, rendering, painting | Janky animations, slow interactions |
| **Lighthouse** | LCP, FID/INP, CLS, TTI, bundle size | Overall page health score |
| **Network** | Waterfall, asset sizes, cache status | Slow loads, unnecessary requests |
| **Memory** | Heap snapshots, allocation timeline | Memory leaks, growing detached DOM |
| **Coverage** | Used vs unused JS/CSS bytes | Dead code, over-imported libraries |
### Core Web Vitals
| Metric | Target | What it measures |
|---|---|---|
| **LCP** (Largest Contentful Paint) | < 2.5s | How fast the main content appears |
| **INP** (Interaction to Next Paint) | < 200ms | How fast interactions respond |
| **CLS** (Cumulative Layout Shift) | < 0.1 | How much the layout shifts during load |
| **TTFB** (Time to First Byte) | < 800ms | Server response time |
| **FCP** (First Contentful Paint) | < 1.8s | How fast anything renders |
### Lighthouse CLI (Headless — for Scripted Analysis)
```bash
# Run Lighthouse from CLI (no browser GUI needed)
npx lighthouse http://localhost:4173 \
--output=json \
--output-path=./lighthouse-report.json \
--chrome-flags="--headless --no-sandbox" \
--only-categories=performance \
--preset=desktop
# For mobile (default):
npx lighthouse http://localhost:4173 \
--output=json \
--output-path=./lighthouse-report.json \
--chrome-flags="--headless --no-sandbox" \
--only-categories=performance
```
**Agent-readable extraction:**
```bash
# Extract key metrics from Lighthouse JSON
cat lighthouse-report.json | node -e "
const r = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
const a = r.audits;
console.log('| Metric | Score | Value |');
console.log('|---|---|---|');
['first-contentful-paint','largest-contentful-paint','total-blocking-time',
'cumulative-layout-shift','speed-index','interactive'].forEach(k => {
if(a[k]) console.log('| ' + a[k].title + ' | ' + (a[k].score*100) + ' | ' + a[k].displayValue + ' |');
});
"
```
---
## Vue/Vite-Specific Optimization Patterns
### Pattern: Vendor Chunk Splitting (Vite)
**Symptom:** Every deploy invalidates the entire JS bundle cache, including stable vendor libs (Vue, Supabase JS). See agnostic principle: *Artifact Partitioning by Change Frequency* in SKILL.md.
**Fix:** Add `manualChunks` to `vite.config.ts`:
```typescript
build: {
rollupOptions: {
output: {
manualChunks: {
'vendor-vue': ['vue', 'vue-router', 'pinia'],
'vendor-supabase': ['@supabase/supabase-js'],
},
},
},
},
```
**Group by change frequency:**
- `vendor-vue`: framework runtime — changes only on major upgrades
- `vendor-supabase`: SDK/client — changes infrequently
- App code (auto): changes every deploy
**Verification:** After `vite build`, check `dist/assets/` for `vendor-vue-*.js` and `vendor-supabase-*.js` chunks.
### Pattern: Resource Hint Pipeline (HTML)
**Symptom:** Fonts load late because browser discovers them only after parsing CSS. See agnostic principle: *Dependency Discovery Parallelization* in SKILL.md.
**Fix:** Replace CSS `@import` with HTML `<link>` tags and add `preconnect` hints:
```html
<!-- In index.html <head> — BEFORE the CSS link -->
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=...&display=swap">
```
Then remove the `@import url(...)` from your CSS file.
**Safety:** Only `preconnect` to domains used on every page load. For runtime-determined domains (e.g., Supabase URL from env vars), skip the hint — hardcoding would break across environments.
### Pattern: Route-Level Code Splitting
**Symptom:** Lighthouse reports a large initial JS bundle with low Coverage score. All routes are in a single chunk.
**Fix:** Use dynamic imports for route components in Vue Router:
```typescript
// ❌ Eager import — everything in one chunk
import SiraatView from '@/views/SiraatView.vue'
// ✅ Lazy route — separate chunk, loaded on navigation
const SiraatView = () => import('@/views/SiraatView.vue')
```
**Verification:** After build, check `dist/assets/` — each lazy route should produce a separate `.js` chunk.
### Pattern: Component Lazy Loading
**Symptom:** Heavy components (charts, editors, modals) are loaded even when not visible.
**Fix:** Use `defineAsyncComponent` for below-the-fold or conditionally-rendered components:
```typescript
import { defineAsyncComponent } from 'vue'
const HeavyChart = defineAsyncComponent(() =>
import('@/features/analytics/HeavyChart.vue')
)
```
### Pattern: Pinia Store Hydration
**Symptom:** Multiple API calls for the same data on page load. Network waterfall shows redundant fetches.
**Fix:** Deduplicate fetches in Pinia stores with loading-state guards:
```typescript
const store = defineStore('lesson', () => {
const data = ref<Lesson | null>(null)
const loading = ref(false)
async function fetch(id: string) {
if (loading.value) return // ← prevent double-fetch
if (data.value?.id === id) return // ← already have it
loading.value = true
try {
data.value = await api.getLesson(id)
} finally {
loading.value = false
}
}
return { data, loading, fetch }
})
```
### Pattern: Image Optimization
**Symptom:** Lighthouse flags large images (LCP degradation). Network shows uncompressed PNGs.
**Fix checklist:**
1. Use `<img loading="lazy">` for below-the-fold images
2. Use WebP/AVIF formats (Vite handles this with plugins)
3. Specify explicit `width` and `height` attributes (prevents CLS)
4. Use `<picture>` with `srcset` for responsive images
### Pattern: Watchers / Computed Overuse
**Symptom:** DevTools Performance tab shows repeated "Scripting" blocks during reactivity updates.
**Fix:**
- Prefer `computed` over `watch` — computed values are lazy and cached
- Use `watchEffect` only when side effects are truly needed
- Use `shallowRef` / `shallowReactive` for large objects that don't need deep reactivity
```typescript
// ❌ Deep reactive on a 1000-item array — Vue tracks every nested property
const items = ref<Item[]>([])
// ✅ Shallow — Vue only tracks the array reference, not individual items
const items = shallowRef<Item[]>([])
```
### Pattern: Virtual Scrolling for Long Lists
**Symptom:** Rendering > 100 DOM nodes causes jank. DevTools shows long "Rendering" and "Painting" phases.
**Fix:** Use virtual scrolling (only render visible items):
```bash
npm install @tanstack/vue-virtual
```
### Pattern: PWA Cache Strategy Audit
**Symptom:** Repeat visits are slow despite service worker. Network tab shows cache misses.
**Audit checklist:**
1. Verify `navigator.serviceWorker.controller` is active (DevTools → Application → Service Workers)
2. Check `Cache Storage` in DevTools — are the expected caches populated?
3. Verify `runtimeCaching` patterns in `vite.config.ts` match actual API URL patterns
4. Check `networkTimeoutSeconds` — too high means slow fallback to cache
### Pattern: Tailwind CSS Purging
**Symptom:** Large CSS bundle despite using Tailwind. Build output shows > 50KB CSS.
**Fix:** Tailwind v4 (your version) uses automatic content detection. Verify:
1. No `@import` of the full Tailwind CSS build in production
2. The `@tailwindcss/vite` plugin is used (it is — confirmed in your config)
3. No dynamic class names that prevent tree-shaking (e.g., `class={`text-${color}-500`}`)
---
## Vue/Vite-Specific Anti-Patterns
1. **Don't eagerly import all routes.** This defeats Vite's code splitting. Always use dynamic `() => import()` for route components.
2. **Don't use `v-if` + `v-for` on the same element.** `v-if` has higher priority in Vue 3 — use a `<template>` wrapper.
3. **Don't deep-watch large arrays.** Use `shallowRef` or `watch(() => arr.length)` to avoid O(n) reactivity tracking.
4. **Don't ignore `key` in `v-for`.** Missing keys force Vue to re-render entire lists instead of patching individual items.
5. **Don't inline large SVGs when they could be `<img>` references.** Inline SVGs increase HTML parse time and bundle size.
6. **Don't load Supabase JS in every component.** Import the singleton client from `supabaseClient.ts` — avoid creating multiple GoTrue instances.
7. **Don't use plain functions for reactive template expressions.** If `v-if="isVisible()"` calls a function, Vue re-executes it on every render cycle. Use `computed` instead — Vue caches the result and only re-runs when dependencies change.
---
## Irreducible Floors in Vue/Vite/Browser
| Cost | What it is | Why it's irreducible |
|---|---|---|
| Vue runtime | Reactivity system, virtual DOM diffing | Framework overhead — ~30KB min gzipped |
| Supabase JS | GoTrue + PostgREST client | Auth + API client — ~40KB min gzipped |
| TLS handshake | HTTPS connection setup | Network latency — mitigated by HTTP/2, preconnect |
| Font loading | WOFF2 download + layout shift | Use `font-display: swap` and preload critical fonts |
| Service Worker registration | SW parse + install on first visit | One-time cost per SW update |
| DNS resolution | Supabase API domain lookup | Use `<link rel="dns-prefetch">` |
---
## Data Extraction Scripts
Use the scripts in `scripts/` for automated profiling:
```bash
# Always-works bundle analysis (no Chrome needed) — start here
scripts/frontend-lighthouse.sh bundle ./apps/frontend
# Lighthouse Core Web Vitals (needs Chrome/Chromium)
scripts/frontend-lighthouse.sh lighthouse http://localhost:4173 desktop
# With custom timeout (default: 120s)
LIGHTHOUSE_TIMEOUT=60 scripts/frontend-lighthouse.sh lighthouse http://localhost:4173
```
> **Agent tip:** If Lighthouse hangs (common in headless/CI environments), the script
> auto-kills after the timeout and suggests bundle mode. Always start with `bundle` mode
> since it works everywhere and provides the most actionable data (chunk sizes, vendor
> splitting status, oversized chunk flags).
## Recommended Profiling Workflow
1. **Bundle analysis first:** Run `scripts/frontend-lighthouse.sh bundle ./apps/frontend` to check chunk sizes
2. **Lighthouse desktop + mobile:** Run `scripts/frontend-lighthouse.sh lighthouse <url> desktop` and default (mobile) — compare scores
3. **Runtime profiling:** Use Chrome DevTools Performance tab to record user flows (navigation, exercise completion)
4. **Memory:** Take heap snapshots before and after a user flow — diff for leaks (detached DOM, event listeners)
5. **Coverage:** Run Coverage tool — identify dead JS/CSS shipped to the client
---
## Post-Optimization Clean Sweep Checklist
After applying the main fixes, run this checklist to catch leftover issues.
This was codified from the FATH frontend session (2026-03-23).
### Template Reactivity
- [ ] All `v-if` / `v-show` conditions use `computed` refs, not plain function calls
- [ ] `v-for` lists use stable `:key` values (not array index)
- [ ] No plain functions in template expressions that could be `computed`
### Component Loading
- [ ] All routes use `() => import()` (lazy routes)
- [ ] Modals, drawers, and sheets use `defineAsyncComponent` (they are never visible on initial render)
- [ ] Heavy sub-components within lazy routes use `defineAsyncComponent` if they are conditionally rendered
### Store Health
- [ ] Stores used by multiple co-mounted views have a concurrent-fetch guard (`if (isLoading) return`)
- [ ] No `deep: true` watchers on large arrays — use `shallowRef` + explicit trigger
- [ ] Computed getters are used instead of methods for derived store state
### Bundle Health
- [ ] Vendor libraries are isolated into stable chunks (`manualChunks`)
- [ ] CSS `@import` statements for external stylesheets (fonts, etc.) are replaced with HTML `<link>` tags
- [ ] `<link rel="preconnect">` hints exist for all statically-known third-party origins
- [ ] Supabase JS is imported only once from a singleton module, not in individual components
### Build Verification
- [ ] `npx vite build` — clean output, no warnings
- [ ] `npx vitest run` — all tests pass
- [ ] `npx vue-tsc -b` — no type errors
- [ ] Build output diff — no unexpected chunk size regressions
@@ -0,0 +1,133 @@
# Go Performance Profiling
## Toolchain
| Tool | Purpose | Command |
|---|---|---|
| `go tool pprof` | CPU / heap / block / mutex profiles | `go tool pprof -text -cum -nodecount=30 profile.pb.gz` |
| `go test -bench` | Micro-benchmarks | `go test -bench=BenchmarkX -benchtime=3s -benchmem -count=3` |
| `go test -cpuprofile` | Capture CPU profile during benchmarks | `go test -bench=BenchmarkX -cpuprofile=cpu.prof` |
| `go test -memprofile` | Capture heap profile during benchmarks | `go test -bench=BenchmarkX -memprofile=mem.prof` |
| `go build -gcflags="-m"` | Escape analysis (what allocates on heap) | `go build -gcflags="-m" ./path/to/pkg 2>&1 \| grep "escapes to heap"` |
| `go test -trace` | Execution trace (goroutine scheduling) | `go test -bench=BenchmarkX -trace=trace.out` |
| `benchstat` | Statistical comparison of benchmarks | `benchstat old.txt new.txt` |
## Data Extraction Script
Use `scripts/go-pprof.sh` to extract profiles into agent-readable markdown.
```bash
# CPU profile from a .prof file
.agents/skills/perf-optimization/scripts/go-pprof.sh cpu path/to/cpu.prof
# Heap profile from a live endpoint
.agents/skills/perf-optimization/scripts/go-pprof.sh heap http://localhost:6060/debug/pprof/heap
# CPU profile from a benchmark (generates and analyzes in one step)
.agents/skills/perf-optimization/scripts/go-pprof.sh bench ./internal/platform/server/... BenchmarkRequireAuth
```
## Go-Specific Optimization Patterns
### Escape Analysis
Before optimizing allocations, run escape analysis to understand WHY values escape to heap:
```bash
go build -gcflags="-m" ./internal/platform/server/ 2>&1 | grep "escapes to heap"
```
Common escape reasons:
- Returning a pointer to a local variable
- Storing a value in an interface (boxing)
- Closure captures
- Slice/map growth beyond initial capacity
### `sync.Pool` for Hot-Path Allocations
**When to use:** The profiler shows a specific type allocated/freed thousands of times per second on the same hot path.
**When NOT to use:** Objects that are long-lived, vary wildly in size, or have cleanup requirements.
```go
var bufPool = sync.Pool{
New: func() interface{} { return new(bytes.Buffer) },
}
func process() {
buf := bufPool.Get().(*bytes.Buffer)
defer func() { buf.Reset(); bufPool.Put(buf) }()
// use buf...
}
```
### Pre-sized Slices and Maps
**Symptom:** `runtime.growslice` or `runtime.mapassign` in the profile — the runtime is repeatedly growing backing arrays.
```go
// ❌ Grows 3 times: cap 0 → 1 → 2 → 4
items := []Item{}
for _, raw := range data { items = append(items, parse(raw)) }
// ✅ One allocation
items := make([]Item, 0, len(data))
for _, raw := range data { items = append(items, parse(raw)) }
```
### `strings.Builder` Over `fmt.Sprintf` in Loops
**Symptom:** `fmt.Sprintf` or `runtime.convTstring` in allocation-heavy loops.
```go
var b strings.Builder
b.Grow(estimatedLen)
for _, part := range parts { b.WriteString(part) }
result := b.String()
```
### Benchmark Best Practices
```go
func BenchmarkX(b *testing.B) {
// Setup OUTSIDE the loop — don't measure init cost
fixture := buildFixture(b)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := doWork(fixture)
// Prevent compiler from eliminating the call
_ = result
}
}
```
**Statistical validity:** Always run with `-count=5` minimum and compare with `benchstat`:
```bash
go test -bench=BenchmarkX -benchmem -count=5 > old.txt
# ... make changes ...
go test -bench=BenchmarkX -benchmem -count=5 > new.txt
benchstat old.txt new.txt
```
## Go-Specific Anti-Patterns
1. **Don't use `sync.Pool` for everything.** It's only effective for truly hot paths with uniform objects. Misuse adds complexity without measurable gain.
2. **Don't fight the GC.** Go's GC is tuned for latency. If throughput matters more, tune `GOGC` before rewriting code.
3. **Don't use `unsafe` for performance.** The compiler's escape analysis and inlining are usually sufficient. `unsafe` breaks memory safety guarantees.
4. **Don't ignore `-race` when benchmarking.** Always verify correctness with `-race` before measuring performance without it.
## Irreducible Floors in Go
These are performance costs you cannot eliminate — recognize them and stop:
| Function | What it is | Why it's irreducible |
|---|---|---|
| `p256MulInternal` | P-256 scalar multiplication (ECDSA) | Hardware-optimized assembly |
| `runtime.mallocgc` | Go memory allocator | Symptom, not cause — fix the caller |
| `runtime.gcBgMarkWorker` | GC mark phase | Proportional to allocation rate — fix allocs |
| `runtime.futex` / `runtime.usleep` | OS synchronization | Scheduling overhead |
| `syscall.Syscall` | Kernel calls (I/O) | Irreducible for actual I/O ops |
| `crypto/aes` | AES encryption | Hardware AES-NI, cannot improve |
@@ -0,0 +1,12 @@
# Python Performance Profiling
> **Stub:** This module will be populated when Python profiling is needed in a project.
> Follow the pattern established in [go.md](go.md).
## Expected Content
- Toolchain: `py-spy`, `cProfile`, `memory_profiler`, `line_profiler`, `scalene`
- Patterns: `__slots__`, generator expressions, `collections.deque`, `numpy` vectorization
- Benchmarking: `timeit`, `pytest-benchmark`
- Irreducible floors: GIL contention, interpreter overhead, C extension boundaries
- Script: `scripts/python-pyspy.sh`
@@ -0,0 +1,12 @@
# Rust Performance Profiling
> **Stub:** This module will be populated when Rust profiling is needed in a project.
> Follow the pattern established in [go.md](go.md).
## Expected Content
- Toolchain: `perf`, `flamegraph`, `criterion`, `cargo-flamegraph`, `dhat`
- Patterns: Arena allocation, `#[inline]`, zero-copy parsing, `Box` vs stack
- Benchmarking: `criterion` setup, statistical comparison
- Irreducible floors: syscall overhead, TLS handshake, memory-mapped I/O
- Script: `scripts/rust-flamegraph.sh`
@@ -0,0 +1,374 @@
#!/usr/bin/env bash
# frontend-lighthouse.sh — Robust frontend performance profiling for AI agents.
#
# Two modes:
# 1. `lighthouse` — Run headless Lighthouse with timeout and binary discovery
# 2. `bundle` — Analyze Vite build output (always works, no Chrome needed)
#
# Usage:
# frontend-lighthouse.sh lighthouse <url> [preset]
# frontend-lighthouse.sh bundle <frontend-dir>
#
# Arguments:
# lighthouse mode:
# url — The URL to audit (e.g., http://localhost:4173)
# preset — "desktop" or "mobile" (default: mobile)
#
# bundle mode:
# frontend-dir — Path to the frontend project root (must have vite.config.*)
#
# Examples:
# frontend-lighthouse.sh lighthouse http://localhost:4173 desktop
# frontend-lighthouse.sh bundle ./apps/frontend
# frontend-lighthouse.sh bundle /home/user/project/apps/frontend
#
# Prerequisites:
# lighthouse mode: Node.js + Chrome/Chromium (or globally installed lighthouse)
# bundle mode: Node.js + Vite project (no Chrome needed)
#
# Robustness features (learned from real-world agent sessions):
# - Binary discovery: checks PATH, npx, ~/.npm-global/bin, common install locations
# - Timeout: kills Lighthouse after LIGHTHOUSE_TIMEOUT seconds (default: 120)
# - Fallback: if Lighthouse fails/hangs, suggests running bundle mode instead
# - Error reporting disabled: prevents interactive consent prompts
# - Bundle mode: always works, extracts chunk sizes from vite build output
#
# Output: Structured markdown tables suitable for LLM consumption.
set -euo pipefail
MODE="${1:-}"
LIGHTHOUSE_TIMEOUT="${LIGHTHOUSE_TIMEOUT:-120}"
# ─── Binary Discovery ──────────────────────────────────────────────────────────
# Lighthouse can be installed globally via npm, in ~/.npm-global, or via npx.
# Try multiple locations to find a working binary.
find_lighthouse() {
# 1. Check PATH
if command -v lighthouse &>/dev/null; then
echo "lighthouse"
return
fi
# 2. Check common global install locations
local candidates=(
"$HOME/.npm-global/bin/lighthouse"
"/usr/local/bin/lighthouse"
"/usr/bin/lighthouse"
)
for candidate in "${candidates[@]}"; do
if [[ -x "$candidate" ]]; then
echo "$candidate"
return
fi
done
# 3. Fall back to npx (will auto-install if needed)
echo "npx --yes lighthouse"
}
# ─── Lighthouse Mode ───────────────────────────────────────────────────────────
run_lighthouse() {
local url="$1"
local preset="${2:-mobile}"
local lh_cmd
lh_cmd=$(find_lighthouse)
local preset_flag=""
if [[ "$preset" == "desktop" ]]; then
preset_flag="--preset=desktop"
fi
local tmpfile
tmpfile=$(mktemp /tmp/lighthouse-XXXXXX.json)
trap 'rm -f "$tmpfile"' EXIT
echo "# Lighthouse Report: ${url} (${preset})"
echo ""
echo "Binary: ${lh_cmd}"
echo "Timeout: ${LIGHTHOUSE_TIMEOUT}s"
echo ""
# Run Lighthouse with timeout to prevent infinite hangs in headless environments.
# --enable-error-reporting=false prevents interactive consent prompts.
# --no-sandbox is required in containerized/CI environments.
local lh_pid
local lh_exit=0
$lh_cmd "$url" \
--output=json \
--output-path="$tmpfile" \
--chrome-flags="--headless --no-sandbox --disable-gpu --disable-dev-shm-usage" \
--only-categories=performance \
$preset_flag \
--enable-error-reporting=false \
--quiet 2>/dev/null &
lh_pid=$!
# Wait with timeout — kill if Lighthouse hangs (common in headless/CI)
local elapsed=0
while kill -0 "$lh_pid" 2>/dev/null; do
if (( elapsed >= LIGHTHOUSE_TIMEOUT )); then
kill -9 "$lh_pid" 2>/dev/null || true
wait "$lh_pid" 2>/dev/null || true
echo "⚠️ Lighthouse timed out after ${LIGHTHOUSE_TIMEOUT}s."
echo ""
echo "This usually means Chrome/Chromium is not available or the headless"
echo "environment doesn't support it. Try bundle mode instead:"
echo ""
echo " $0 bundle <frontend-dir>"
echo ""
return 1
fi
sleep 2
elapsed=$((elapsed + 2))
done
wait "$lh_pid" || lh_exit=$?
if [[ $lh_exit -ne 0 ]]; then
echo "⚠️ Lighthouse exited with code ${lh_exit}."
echo "Try bundle mode instead: $0 bundle <frontend-dir>"
return 1
fi
if [[ ! -s "$tmpfile" ]]; then
echo "⚠️ Lighthouse produced no output."
echo "Try bundle mode instead: $0 bundle <frontend-dir>"
return 1
fi
# Extract metrics into structured markdown
node -e "
const fs = require('fs');
const r = JSON.parse(fs.readFileSync('$tmpfile', 'utf8'));
const a = r.audits;
const score = Math.round((r.categories.performance.score || 0) * 100);
console.log('## Performance Score: ' + score + '/100');
console.log('');
console.log('## Core Web Vitals');
console.log('');
console.log('| Metric | Value | Score |');
console.log('|---|---|---|');
const vitals = [
'first-contentful-paint',
'largest-contentful-paint',
'total-blocking-time',
'cumulative-layout-shift',
'speed-index',
'interactive',
];
vitals.forEach(k => {
if (a[k]) {
const s = a[k].score !== null ? Math.round(a[k].score * 100) : 'N/A';
console.log('| ' + a[k].title + ' | ' + (a[k].displayValue || 'N/A') + ' | ' + s + ' |');
}
});
console.log('');
console.log('## Opportunities');
console.log('');
const opportunities = Object.values(a)
.filter(audit => audit.details && audit.details.type === 'opportunity' && audit.details.overallSavingsMs > 0)
.sort((x, y) => (y.details.overallSavingsMs || 0) - (x.details.overallSavingsMs || 0));
if (opportunities.length > 0) {
console.log('| Opportunity | Potential Savings |');
console.log('|---|---|');
opportunities.forEach(o => {
console.log('| ' + o.title + ' | ' + Math.round(o.details.overallSavingsMs) + ' ms |');
});
} else {
console.log('No significant opportunities found.');
}
console.log('');
console.log('## Diagnostics');
console.log('');
const diagnostics = Object.values(a)
.filter(audit => audit.details && audit.details.type === 'table' && audit.score !== null && audit.score < 0.9)
.slice(0, 10);
if (diagnostics.length > 0) {
console.log('| Diagnostic | Score |');
console.log('|---|---|');
diagnostics.forEach(d => {
console.log('| ' + d.title + ' | ' + Math.round(d.score * 100) + ' |');
});
} else {
console.log('All diagnostics passed.');
}
"
}
# ─── Bundle Analysis Mode ──────────────────────────────────────────────────────
# Always works — no Chrome needed. Extracts chunk sizes from vite build output.
# This was the most useful data source during the FATH frontend session: it
# revealed the 297KB monolithic index chunk and guided all 6 fixes.
run_bundle() {
local frontend_dir="$1"
if [[ ! -d "$frontend_dir" ]]; then
echo "Error: directory '$frontend_dir' does not exist."
exit 1
fi
# Verify it's a Vite project
if ! ls "$frontend_dir"/vite.config.* &>/dev/null; then
echo "Error: no vite.config.* found in '$frontend_dir'. Is this a Vite project?"
exit 1
fi
echo "# Bundle Analysis: ${frontend_dir}"
echo ""
echo "Running \`vite build\`..."
echo ""
local build_output
build_output=$(cd "$frontend_dir" && npx vite build 2>&1)
local build_exit=$?
if [[ $build_exit -ne 0 ]]; then
echo "⚠️ Build failed (exit code $build_exit):"
echo ""
echo '```'
echo "$build_output"
echo '```'
return 1
fi
# Parse build output into structured tables
node -e "
const output = \`$build_output\`;
const lines = output.split('\n');
const jsChunks = [];
const cssChunks = [];
// Parse Vite build output lines like:
// dist/assets/index-BExof8y3.js 20.04 kB │ gzip: 6.90 kB
for (const line of lines) {
const match = line.match(/dist\/assets\/(.+?)\s+([\d.]+)\s*kB\s*│\s*gzip:\s*([\d.]+)\s*kB/);
if (match) {
const [, name, rawKB, gzipKB] = match;
const entry = { name, raw: parseFloat(rawKB), gzip: parseFloat(gzipKB) };
if (name.endsWith('.js')) jsChunks.push(entry);
else if (name.endsWith('.css')) cssChunks.push(entry);
}
}
// Sort by gzip size descending
jsChunks.sort((a, b) => b.gzip - a.gzip);
cssChunks.sort((a, b) => b.gzip - a.gzip);
const totalJsRaw = jsChunks.reduce((s, c) => s + c.raw, 0);
const totalJsGzip = jsChunks.reduce((s, c) => s + c.gzip, 0);
const totalCssRaw = cssChunks.reduce((s, c) => s + c.raw, 0);
const totalCssGzip = cssChunks.reduce((s, c) => s + c.gzip, 0);
console.log('## JS Chunks (sorted by gzip size)');
console.log('');
console.log('| Chunk | Raw (KB) | Gzip (KB) |');
console.log('|---|---|---|');
jsChunks.forEach(c => {
// Clean chunk name: remove hash suffix for readability
const clean = c.name.replace(/-[A-Za-z0-9_-]{8}\\.js$/, '.js');
console.log('| ' + clean + ' | ' + c.raw.toFixed(2) + ' | ' + c.gzip.toFixed(2) + ' |');
});
console.log('| **Total JS** | **' + totalJsRaw.toFixed(2) + '** | **' + totalJsGzip.toFixed(2) + '** |');
if (cssChunks.length > 0) {
console.log('');
console.log('## CSS Chunks');
console.log('');
console.log('| Chunk | Raw (KB) | Gzip (KB) |');
console.log('|---|---|---|');
cssChunks.forEach(c => {
const clean = c.name.replace(/-[A-Za-z0-9_-]{8}\\.css$/, '.css');
console.log('| ' + clean + ' | ' + c.raw.toFixed(2) + ' | ' + c.gzip.toFixed(2) + ' |');
});
console.log('| **Total CSS** | **' + totalCssRaw.toFixed(2) + '** | **' + totalCssGzip.toFixed(2) + '** |');
}
console.log('');
console.log('## Summary');
console.log('');
console.log('| Metric | Value |');
console.log('|---|---|');
console.log('| JS chunks | ' + jsChunks.length + ' |');
console.log('| CSS chunks | ' + cssChunks.length + ' |');
console.log('| Total JS (gzip) | ' + totalJsGzip.toFixed(2) + ' KB |');
console.log('| Total CSS (gzip) | ' + totalCssGzip.toFixed(2) + ' KB |');
console.log('| Largest JS chunk | ' + (jsChunks[0]?.name || 'N/A') + ' (' + (jsChunks[0]?.gzip.toFixed(2) || '0') + ' KB gzip) |');
// Flag potential issues
console.log('');
console.log('## Flags');
console.log('');
const bigChunks = jsChunks.filter(c => c.gzip > 50);
if (bigChunks.length > 0) {
console.log('⚠️ **Large chunks (>50 KB gzip):**');
bigChunks.forEach(c => {
console.log(' - ' + c.name + ': ' + c.gzip.toFixed(2) + ' KB gzip');
});
console.log('');
console.log('Consider splitting vendor libraries into separate chunks (see: Artifact Partitioning pattern in SKILL.md).');
} else {
console.log('✅ No oversized chunks detected.');
}
const vendorChunks = jsChunks.filter(c => c.name.startsWith('vendor-'));
if (vendorChunks.length > 0) {
console.log('');
console.log('✅ Vendor chunk splitting detected: ' + vendorChunks.map(c => c.name).join(', '));
} else if (jsChunks.some(c => c.gzip > 40)) {
console.log('');
console.log('⚠️ No vendor chunk splitting detected. The largest chunk may contain framework + app code bundled together.');
}
"
}
# ─── Main ──────────────────────────────────────────────────────────────────────
case "${MODE}" in
lighthouse|lh)
URL="${2:-}"
PRESET="${3:-mobile}"
if [[ -z "$URL" ]]; then
echo "Usage: $0 lighthouse <url> [desktop|mobile]"
echo " $0 bundle <frontend-dir>"
exit 1
fi
run_lighthouse "$URL" "$PRESET"
;;
bundle|build)
DIR="${2:-}"
if [[ -z "$DIR" ]]; then
echo "Usage: $0 bundle <frontend-dir>"
echo " $0 lighthouse <url> [desktop|mobile]"
exit 1
fi
run_bundle "$DIR"
;;
*)
echo "Frontend Performance Profiling Script"
echo ""
echo "Usage:"
echo " $0 lighthouse <url> [desktop|mobile] — Run Lighthouse (needs Chrome)"
echo " $0 bundle <frontend-dir> — Analyze Vite build chunks (no Chrome)"
echo ""
echo "Examples:"
echo " $0 lighthouse http://localhost:4173 desktop"
echo " $0 bundle ./apps/frontend"
echo ""
echo "Environment:"
echo " LIGHTHOUSE_TIMEOUT=120 — Seconds before killing a hung Lighthouse (default: 120)"
exit 1
;;
esac
@@ -0,0 +1,112 @@
#!/usr/bin/env bash
# go-pprof.sh — Extract Go pprof profiles into agent-readable markdown.
#
# Usage:
# go-pprof.sh cpu <profile-path-or-url> [top-n]
# go-pprof.sh heap <profile-path-or-url> [top-n]
# go-pprof.sh bench <package-path> <bench-regex> [top-n]
#
# Examples:
# go-pprof.sh cpu cpu.prof
# go-pprof.sh heap http://localhost:6060/debug/pprof/heap
# go-pprof.sh bench ./internal/platform/server/... BenchmarkRequireAuth 40
#
# Output: Structured markdown suitable for LLM consumption.
set -euo pipefail
PROFILE_TYPE="${1:-}"
SOURCE="${2:-}"
TOP_N="${3:-30}"
usage() {
echo "Usage:"
echo " $0 cpu <profile-path-or-url> [top-n]"
echo " $0 heap <profile-path-or-url> [top-n]"
echo " $0 bench <package-path> <bench-regex> [top-n]"
exit 1
}
if [[ -z "$PROFILE_TYPE" || -z "$SOURCE" ]]; then
usage
fi
extract_pprof() {
local profile_file="$1"
local label="$2"
local top="$3"
echo ""
echo "--- PPROF ${label} PROFILE (Top ${top} by Cumulative) ---"
echo ""
echo '```'
go tool pprof -text -cum -nodecount="$top" "$profile_file" 2>/dev/null \
| grep -v "^Showing nodes" \
| sed '/^$/d'
echo '```'
echo ""
}
case "$PROFILE_TYPE" in
cpu|heap|block|mutex)
extract_pprof "$SOURCE" "$(echo "$PROFILE_TYPE" | tr '[:lower:]' '[:upper:]')" "$TOP_N"
;;
bench)
BENCH_REGEX="${3:-}"
TOP_N="${4:-30}"
if [[ -z "$BENCH_REGEX" ]]; then
echo "Error: bench mode requires <package-path> and <bench-regex>"
usage
fi
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT
echo "# Benchmark Profile: ${BENCH_REGEX}"
echo ""
# Run benchmark and capture results + profiles
echo "## Benchmark Results"
echo ""
echo '```'
go test "$SOURCE" \
-bench="$BENCH_REGEX" \
-benchtime=2s \
-benchmem \
-count=1 \
-run='^$' \
-cpuprofile="$TMPDIR/cpu.prof" \
-memprofile="$TMPDIR/mem.prof" \
-timeout=120s 2>&1
echo '```'
# Extract CPU profile
if [[ -f "$TMPDIR/cpu.prof" ]]; then
# Find the test binary (go test leaves it next to the profile)
TEST_BIN=$(find "$TMPDIR" -name '*.test' 2>/dev/null | head -1 || true)
if [[ -n "$TEST_BIN" ]]; then
extract_pprof "$TMPDIR/cpu.prof" "CPU" "$TOP_N"
else
extract_pprof "$TMPDIR/cpu.prof" "CPU" "$TOP_N"
fi
fi
# Extract heap profile
if [[ -f "$TMPDIR/mem.prof" ]]; then
echo ""
echo "--- PPROF HEAP PROFILE (Top ${TOP_N} by Cumulative, alloc_space) ---"
echo ""
echo '```'
go tool pprof -text -cum -nodecount="$TOP_N" -alloc_space "$TMPDIR/mem.prof" 2>/dev/null \
| grep -v "^Showing nodes" \
| sed '/^$/d'
echo '```'
echo ""
fi
;;
*)
echo "Error: unknown profile type '$PROFILE_TYPE'"
usage
;;
esac
+129
View File
@@ -0,0 +1,129 @@
---
name: sequential-thinking
description: Performs dynamic, reflective problem-solving through iterative thought chains. Use for complex planning requiring revision, branching, backtracking, or hypothesis verification. Ideal for multi-step analysis where context maintenance is required or the full scope isn't initially clear.
---
# Sequential Thinking
A structured approach to complex problem-solving that breaks down challenges into iterative thought steps with built-in flexibility for revision and course correction.
## When to Use This Skill
- Breaking down complex problems into manageable steps
- Planning and design requiring iterative refinement
- Analysis that might need course correction mid-stream
- Problems where the full scope emerges during analysis
- Multi-step solutions requiring context across steps
- Filtering out irrelevant information
- Hypothesis generation and verification workflows
## Core Methodology
Sequential thinking follows a dynamic process:
1. **Initial estimation**: Start with an estimate of thoughts needed, but remain flexible
2. **Iterative analysis**: Work through thoughts sequentially while building context
3. **Revision capability**: Question or revise previous thoughts as understanding deepens
4. **Branch exploration**: Explore alternative approaches when needed
5. **Hypothesis cycle**: Generate hypotheses, verify against thought chain, repeat
6. **Convergence**: Continue until reaching a satisfactory solution
## Instructions
### Thought Structure
Each thought in the sequence should include:
- **thought**: Current thinking step content
- **thoughtNumber**: Position in sequence (1, 2, 3, ...)
- **totalThoughts**: Current estimate of total thoughts needed (adjustable)
- **nextThoughtNeeded**: Whether another thought step is required
Optional revision/branching metadata:
- **isRevision**: Boolean indicating if reconsidering previous thinking
- **revisesThought**: Which thought number is being revised
- **branchFromThought**: Branching point thought number
- **branchId**: Identifier for current branch
- **needsMoreThoughts**: Flag when reaching end but requiring more analysis
### Process Guidelines
**Starting out:**
- Estimate initial thoughts needed based on problem complexity
- Begin with thought 1, establishing context and approach
- Set totalThoughts conservatively; you can adjust later
**During analysis:**
- Build on previous thoughts while maintaining context
- Filter out irrelevant information at each step
- Express uncertainty when present
- Don't hesitate to revise if you spot errors or better approaches
- Adjust totalThoughts up/down as the problem's scope becomes clearer
**Revision pattern:**
When reconsidering previous thinking:
```json
{
"thought": "On reflection, thought 3's assumption about X was incorrect because Y...",
"thoughtNumber": 6,
"totalThoughts": 10,
"isRevision": True,
"revisesThought": 3,
"nextThoughtNeeded": True
}
```
**Hypothesis cycle:**
1. Generate hypothesis based on current understanding
2. Verify against previous thought chain
3. If verification fails, revise or branch
4. Repeat until hypothesis is validated
**Completion:**
- Only set `nextThoughtNeeded: False` when truly satisfied with the solution
- Provide a single, clear final answer
- Ensure the answer directly addresses the original problem
### Working with Context
**Maintain continuity:**
- Reference specific previous thoughts by number
- Build logical connections between thoughts
- Track which thoughts are still valid vs. revised
**Filter information:**
- Ignore details irrelevant to current thought step
- Focus on information that advances understanding
- Re-evaluate relevance as context evolves
**Manage complexity:**
- If a thought becomes too complex, break it into multiple thoughts
- Increase totalThoughts estimate accordingly
- Keep each individual thought focused
### Output Format
Present your sequential thinking in a structured format:
```
Thought [N/Total]: [Current thought content]
[If revision: "This revises thought X because..."]
[If branching: "Branching from thought X to explore..."]
[Continue with next thought when nextThoughtNeeded is True]
Final output after all thoughts complete:
Solution: [Clear, direct answer to the original problem]
```
## Examples
For concrete examples of sequential thinking in action, see `resources/examples.md`.
## Key Principles
- **Flexibility over rigidity**: Adjust your approach as understanding deepens
- **Revision is strength**: Correcting course shows good reasoning
- **Hypothesis-driven**: Generate and test hypotheses iteratively
- **Context-aware**: Maintain awareness of previous thoughts while progressing
- **Clarity at completion**: Deliver a single, clear final answer
@@ -0,0 +1,65 @@
# Sequential Thinking Examples
## Example 1: Simple Math Problem
**Problem:** If a train travels 120 km in 2 hours, then 180 km in the next 3 hours, what's the average speed?
**Thought 1/5:** Breaking down the problem - we have two segments of travel with different distances and times. Need to calculate total distance and total time first.
**Thought 2/5:** First segment: 120 km in 2 hours. Second segment: 180 km in 3 hours. Total distance = 120 + 180 = 300 km. Total time = 2 + 3 = 5 hours.
**Thought 3/5:** Average speed formula = Total distance / Total time = 300 km / 5 hours = 60 km/h.
**Thought 4/5:** Verifying: Does this make sense? First segment speed = 60 km/h, second segment = 60 km/h. Wait, that can't be right.
**Thought 5/6 (Revision of Thought 4):** Let me recalculate segment speeds. First: 120/2 = 60 km/h. Second: 180/3 = 60 km/h. Actually, both segments are at 60 km/h, so average is correctly 60 km/h. My suspicion was unfounded.
**Solution:** The average speed is 60 km/h.
## Example 2: Complex Design Decision with Branching
**Problem:** Design a caching strategy for a high-traffic API with both user-specific and global data.
**Thought 1/8:** Initial analysis - need to consider cache hit rate, memory usage, invalidation complexity, and latency requirements. Two data types suggest a multi-layered approach.
**Thought 2/8:** Hypothesis: Use Redis with separate key namespaces for user-specific (user:*) and global (global:*) data. User data could have shorter TTL due to personalization.
**Thought 3/8:** Considering TTL strategy - global data changes infrequently, could use 1-hour TTL. User-specific data needs 5-15 minute TTL to stay fresh.
**Thought 4/8:** Wait, need to think about invalidation. What happens when global data updates? Broadcasting invalidation to all instances could be expensive.
**Thought 5/10 (Branching from Thought 4):** Alternative approach - use cache versioning. Include version number in global cache keys. When data updates, increment version. Old cached entries naturally expire.
**Thought 6/10:** This versioning approach adds complexity but eliminates broadcast invalidation. Trade-off: some stale data until TTL expires vs. system complexity.
**Thought 7/10:** For user-specific data, simple TTL should work since each user's data is independent. No cross-user invalidation needed.
**Thought 8/10:** Hypothesis verification - versioned global cache + TTL-based user cache provides good balance. Testing memory usage: assume 100K users, 10KB user cache, 50MB global = ~1GB total. Acceptable.
**Thought 9/10:** Edge case consideration - what if version wraps around? Use timestamp-based versioning (YYYYMMDDHHMMSS) to prevent collisions.
**Thought 10/10:** Final design validated. Global cache with timestamp versioning, user cache with 10-minute TTL. Monitoring on cache hit rates and memory usage post-deployment.
**Solution:** Implement a two-tier Redis caching strategy with timestamp-versioned keys for global data (1-hour TTL) and simple key-value with 10-minute TTL for user-specific data. Monitor hit rates and memory usage after deployment.
## Example 3: Debugging with Multiple Revisions
**Problem:** API returning 500 errors intermittently, logs show database timeout.
**Thought 1/6:** Initial hypothesis - database connection pool exhaustion. Check current pool size and active connections.
**Thought 2/6:** Pool size = 20, monitoring shows peak usage of 18 connections. Not exhausted. Need to look elsewhere.
**Thought 3/7 (Revised estimate):** Query performance issue? Check slow query log. Found several queries taking 5-10 seconds during error periods.
**Thought 4/7:** Slow queries all involve joins on large tables without proper indexes. Hypothesis: missing indexes causing table scans.
**Thought 5/7:** Wait, these queries worked fine last week. What changed? Checking deployment history... no schema changes, no new queries.
**Thought 6/8 (Revision of Thought 4):** The queries themselves haven't changed, but data volume has. Tables grew 300% last week due to marketing campaign. Indexes exist but statistics are stale.
**Thought 7/8:** Hypothesis: Stale query planner statistics causing poor query plans. Solution: Run ANALYZE on affected tables to update statistics.
**Thought 8/8:** Verified - after ANALYZE, query plans improved and execution times dropped to <500ms. Root cause was stale statistics, not missing indexes.
**Solution:** Run ANALYZE on the affected database tables to update query planner statistics. Implement automated ANALYZE job to run after bulk data imports to prevent future occurrences.
+115
View File
@@ -0,0 +1,115 @@
---
description: Research phase - understand context and gather knowledge
---
# Phase 1: Research
## Purpose
Understand the request context AND gather accurate, up-to-date knowledge before writing any code.
## Steps
### 1. Analyze Request
Parse the user's request and identify:
- What are they asking for?
- What is the scope?
### 2. Review Current Implementation
Check the repository to understand:
- Current technical architecture
- Existing implementation patterns
- Dependencies and configurations
### 3. Build Mental Model
Inventory:
- Business requirements
- Technical constraints
- Integration points with existing code
### 4. Define Scope
Use `task_boundary` to set mode to **PLANNING**
Create a plan with bite-sized atomic tasks in `task.md`:
- `[ ]` = Not started
- Use indented lists for sub-items
### 5. Identify Research Topics
List all technologies, libraries, and patterns involved in this task.
Example:
```
Topics for "Task CRUD API":
- Go http.ServeMux routing patterns
- SQLC query generation
- UUID handling in PostgreSQL
- JWT authentication middleware
```
### 6. Search External Documentation
For EACH topic, search external documentation using the best tool available in your environment:
**Documentation search tools (use whichever is available):**
- **Qurio** (`mcp_qurio_qurio_search`, `mcp_qurio_qurio_read_page`) — if Qurio MCP is enabled
- **Context7** or similar MCP documentation servers — if available
- **`search_web` / `read_url_content`** — universal fallback always available
**Search strategy (2-5 keywords per query):**
```
# Using available documentation tool:
search(query="Go ServeMux routing")
search(query="SQLC CRUD queries")
search(query="Pinia store setup")
# For deeper reading on a specific URL:
read_page(url="https://go.dev/doc/<topic>")
```
### 7. Document Findings
Create `docs/research_logs/{feature_name}.md` with:
- Key patterns discovered
- Code examples from documentation
- API signatures and options
- Gotchas or edge cases found
#### Research Log Naming
Each feature should have its own research log:
```
docs/research_logs/
├── epic1_auth.md # Epic 1: Authentication
├── epic2_task.md # Epic 2: Task CRUD
├── epic3_lists.md # Epic 3: Lists
└── {feature_name}.md # Pattern: one log per feature
```
### 8. Document Architecture Decisions
If any decision involves:
- Choosing between 2+ viable approaches
- Introducing a new dependency or pattern
- Changing existing architecture
Then create an ADR using the **ADR Skill** at `docs/decisions/NNNN-short-title.md`.
**Skills to consider:**
- **Sequential Thinking** — for complex design decisions with multiple trade-offs
### 9. Fallback: State Training Data Usage
If relying on training data, explicitly state:
> "I am relying on my training data for this solution as external verification was unavailable."
## If This Phase Fails
If no documentation search tool yields results:
1. Document what was searched
2. State: "Relying on training data"
3. Proceed with caution
4. Flag for human review if critical
## Completion Criteria
- [ ] Request analyzed and scope defined
- [ ] `task.md` created with atomic tasks
- [ ] Research log created at `docs/research_logs/{feature_name}.md`
- [ ] All major technologies researched
- [ ] Code examples documented
- [ ] Architecture decisions documented as ADRs (if applicable)
## Next Phase
Proceed to **Phase 2: Implement** (`/2-implement`)
+101
View File
@@ -0,0 +1,101 @@
---
description: Implement phase - TDD cycle for writing code
---
# Phase 2: Implement
## Purpose
Write production code following Test-Driven Development (TDD).
## Prerequisites
- Phase 1 (Research) completed
- Research log exists at `docs/research_logs/{feature_name}.md`
- Mark task as `[/]` in task.md
## Steps
**Set Mode:** Use `task_boundary` to set mode to **EXECUTION**.
### 1. Create Test File
Co-locate with implementation:
- Go: `*_test.go`
- TypeScript: `*.spec.ts`
### 2. Write Failing Test (Red)
```go
func TestCreateTask_Success(t *testing.T) {
// Arrange
mockStore := NewMockStorage()
service := NewService(mockStore, logger)
// Act
task, err := service.Create(ctx, userID, req)
// Assert
require.NoError(t, err)
assert.Equal(t, "Test Task", task.Title)
}
```
Run test to confirm it fails:
```bash
# Go
go test -v ./internal/features/{feature}/...
# Frontend
pnpm run test -- --watch
```
### 3. Write Minimal Code (Green)
Write ONLY enough code to make the test pass.
Run test to confirm it passes:
```bash
go test -v ./internal/features/{feature}/...
```
### 4. Refactor (Blue)
Improve the code while keeping tests green:
- Improve names and structure
- Remove duplication
- **Follow predefined agent rules** (read applicable `.agents/rules/*.md`)
- **Handle errors** per Error Handling Principles
- **Add logging** per Logging and Observability Mandate
- If the refactoring is complex, activate the **Sequential Thinking** skill
- If debugging a failing test, activate the **Debugging Protocol** skill
- Ensure tests still pass
### 5. Repeat
Continue Red-Green-Refactor for each behavior.
## Unit Test Requirements
- Mock all dependencies (interfaces)
- Test happy path, error paths AND edge cases
- Target >85% coverage on domain logic
## Development Commands
```bash
# Run specific test
go test -v -run TestCreateTask ./internal/features/task/...
# Run with coverage
go test -cover ./internal/features/task/...
# Frontend tests
pnpm run test
pnpm run test -- --coverage
```
## Completion Criteria
- [ ] Unit tests written and passing
- [ ] Implementation complete
- [ ] Error handling follows principles
- [ ] Logging added to operations
- [ ] Code follows project patterns
- [ ] List all I/O adapters modified or created (storage files, external API clients)
- [ ] If adapters were modified: Phase 3 is MANDATORY — do not skip
## Next Phase
Proceed to **Phase 3: Integrate** (`/3-integrate`)
+95
View File
@@ -0,0 +1,95 @@
---
description: Integrate phase - test adapters with real infrastructure
---
# Phase 3: Integrate
## Purpose
Test adapter implementations (database, external APIs) with real infrastructure using Testcontainers.
## Prerequisites
- Phase 2 (Implement) completed
- Unit tests passing
## Applicability (Default: REQUIRED)
This phase is REQUIRED by default. You may ONLY skip it if ALL of the following are true:
- [ ] No storage/repository files were modified or created
- [ ] No external API client files were modified or created
- [ ] No database queries or schemas were changed
- [ ] No message queue, cache, or I/O adapter code was touched
If ANY adapter was modified, you MUST write integration tests.
## If This Phase Fails
If integration tests fail:
1. Check container logs for errors
2. Verify schema matches expectations
3. Fix adapter implementation
4. Re-run tests before proceeding
## Steps
### 1. Setup Testcontainers
**Go Example:**
```go
func TestPostgresStorage_Integration(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
ctx := context.Background()
// Start PostgreSQL container
postgres, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: testcontainers.ContainerRequest{
Image: "postgres:16-alpine",
ExposedPorts: []string{"5432/tcp"},
Env: map[string]string{
"POSTGRES_USER": "test",
"POSTGRES_PASSWORD": "test",
"POSTGRES_DB": "test",
},
WaitingFor: wait.ForListeningPort("5432/tcp"),
},
Started: true,
})
require.NoError(t, err)
defer postgres.Terminate(ctx)
// Get connection string and run tests
}
```
### 2. Write Integration Tests
Test file naming: `*_integration_test.go` or `*.integration.spec.ts`
```go
func TestPostgresStorage_CreateTask(t *testing.T) {
// Uses real PostgreSQL from Testcontainers
storage := NewPostgresStorage(pool)
task, err := storage.Create(ctx, userID, req)
require.NoError(t, err)
assert.NotEqual(t, uuid.Nil, task.ID)
}
```
### 3. Run Integration Tests
```bash
# Go - run all tests including integration
go test -v ./...
```
### 4. Manual Check (Optional)
- If UI involved, launch a browser to verify the basic flow.
- If API involved, use `curl` or `client` to hit the endpoint.
## Completion Criteria
- [ ] Integration tests written for all adapters
- [ ] Tests pass with real infrastructure (Testcontainers)
- [ ] Database queries verified against real PostgreSQL
## Next Phase
Proceed to **Phase 4: Verify** (`/4-verify`)
+87
View File
@@ -0,0 +1,87 @@
---
description: Verify phase - run full validation suite
---
# Phase 4: Verify
## Purpose
Run all linters, static analysis, and tests to ensure code quality.
> **Note:** Paths below follow the project structure defined in `project-structure.md`.
> Adjust paths to match the actual project layout (e.g. `apps/backend`, `apps/frontend`).
### Phase 3 Gate
- [ ] Were any storage/database adapter files modified? List them: ___
- [ ] Were any external API adapters modified? List them: ___
- [ ] If YES to either: are integration tests written and passing?
- [ ] If NO to both: state why Phase 3 was skipped
> CRITICAL: If you modified `*_pg.go`, `*_integration*.go`, or any SQL query, Phase 3 is MANDATORY. Do not proceed without integration tests.
### Phase 3.5 Gate
- [ ] Were any UI changes or modified? List them: ___
- [ ] If YES: are E2E tests written and passing?
- [ ] If NO to both: state why Phase 3.5 was skipped
> CRITICAL: If you modified `frontend/*` or any frontend component and integration point, Phase 3.5 is MANDATORY. Do not proceed without E2E tests.
## If This Phase Fails
If lint/test/build fails:
1. **Do not proceed** to Ship
2. Fix the issue (go back to Phase 2 or 3 as needed)
3. Re-run full verification
4. Only proceed when ALL checks pass
## Steps
**Set Mode:** Use `task_boundary` to set mode to **VERIFICATION**.
### 1. Backend Validation
Run the FULL validation suite for the backend path as defined in `project-structure.md`:
```bash
# // turbo
# Adjust path per project-structure.md (default: apps/backend)
cd apps/backend && gofumpt -l -e -w . && go vet ./... && staticcheck ./... && gosec -quiet ./... && go test -race ./...
```
### 2. Frontend Validation
```bash
# // turbo
# Adjust path per project-structure.md (default: apps/frontend)
cd apps/frontend && pnpm run lint --fix && npx vue-tsc --noEmit && pnpm run test
```
### 3. Build Check
```bash
# Backend (path per project-structure.md)
cd apps/backend && go build ./...
# Frontend (path per project-structure.md)
cd apps/frontend && pnpm run build
```
### 4. Check Coverage
Report actual coverage in task summary.
**Go:**
```bash
go test -cover ./internal/features/...
```
**Frontend:**
```bash
pnpm run test -- --coverage
```
## Completion Criteria
- [ ] All lint checks pass
- [ ] All tests pass
- [ ] Build succeeds
- [ ] Coverage reported (target >85% on domain logic)
## On Success
Mark task as `[x]` in task.md (verification passed = task complete).
## Next Phase
Proceed to **Phase 5: Ship** (`/5-commit`)
+55
View File
@@ -0,0 +1,55 @@
---
description: Git commit with conventional format
---
# Ship: Commit
## Purpose
Commit completed work with proper conventional commit format.
> **Note:** For detailed conventions (branch naming, commit types, PR size, merge strategy),
> see `git-workflow-principles.md` in `.agents/rules/`.
## Prerequisites
- All verification checks pass
- Code is ready for review/merge
## Steps
### 1. Review Changes
```bash
git status
git diff --staged
```
### 2. Stage Changes
```bash
# Stage all changes
git add .
# Or stage selectively (adjust path per project-structure.md)
git add apps/backend/internal/features/task/
```
### 3. Commit with Conventional Format
Follow the format from `git-workflow-principles.md`:
```bash
git commit -m "<type>(<scope>): <description>"
```
**Examples:**
```bash
git commit -m "feat(task): add CRUD API endpoints"
git commit -m "fix(auth): correct token expiry validation"
git commit -m "refactor(storage): extract interface for storage layer"
git commit -m "test(task): add integration tests for storage adapter"
```
### 4. Update task.md
Mark completed items as `[x]` in the task checklist.
## Completion Criteria
- [ ] Changes committed with proper format
- [ ] task.md updated to reflect completion
+224
View File
@@ -0,0 +1,224 @@
---
description: Code audit workflow - structured code review and quality verification
---
# Audit Workflow
## Purpose
Inspect existing code quality and produce structured findings. This workflow does not write new features — it identifies issues for subsequent fix workflows.
## When to Use
- After another agent's feature is committed (cross-agent review)
- Periodic quality gates on the codebase
- Before releases or deployments
- When user wants assurance without writing new code
- After addressing review findings, to verify the fixes
## When NOT to Use
- When writing new features (use `/orchestrator`)
- When fixing known bugs (use `/quick-fix`)
- When restructuring code (use `/refactor`)
## Pre-Audit Checklist
Before starting, you MUST:
1. Scan `.agents/rules/` directory — these form the review criteria
2. Read `rule-priority.md` for conflict resolution
3. Identify the scope of the audit (specific feature, module, or full codebase)
## Phases
### Phase 1: Code Review
**Set Mode:** Use `task_boundary` to set mode to **PLANNING**
Invoke the **Code Review Skill** against the specified files/features.
Review against these categories (in priority order from `rule-priority.md`):
#### 1. Security
- Input validation on all boundaries
- No hardcoded secrets or credentials
- Parameterized queries (no SQL injection)
- Proper authentication/authorization checks
#### 2. Reliability
- Error handling on all I/O operations (no empty catch blocks)
- All resources cleaned up (connections, files, locks)
- Timeouts on external calls
- Graceful degradation patterns
#### 3. Testability
- I/O operations behind interfaces/abstractions
- Business logic is pure (no side effects)
- Dependencies injected, not hardcoded
- Test coverage on critical paths
#### 4. Observability
- All operation entry points logged (start/success/failure)
- Structured logging with correlation IDs
- Appropriate log levels
#### 5. Code Quality
- Follows existing codebase patterns (>80% consistency)
- Functions are focused and small (10-50 lines)
- Clear naming that reveals intent
- No code duplication (DRY)
### Phase 1.5: Cross-Boundary Review
**Set Mode:** Continue in **PLANNING**
Cross-boundary issues live at the seams between components, not inside any single file. This phase defines a **menu of dimensions** — activate only those that apply to the project under audit, and explicitly state which you skipped and why.
#### Dimension Selection
| Dimension | Activate When |
|---|---|
| **A. Integration Contracts** | Project has both a frontend and a backend |
| **B. Database & Schema** | Project uses a relational/document database |
| **C. Configuration & Environment** | Always — universal |
| **D. Dependency Health** | Always — universal |
| **E. Test Coverage Gaps** | Always — universal |
| **F. Mobile ↔ Backend** | Project has a mobile app and a backend |
At the start of this phase you MUST state:
> "Activating dimensions: A, B, C, D, E. Skipping F (no mobile app)."
---
#### Dimension A: Integration Contracts
*Applies to: full-stack projects with frontend + backend*
- [ ] Map every backend endpoint (route + method) against its frontend adapter — flag any unmapped endpoints in either direction
- [ ] Verify request/response field names, types, and status codes match across the boundary
- [ ] Verify all outbound HTTP calls use the project's centralized API client (not raw `fetch`/`axios`) — see `typescript-idioms-and-patterns.md § Centralized HTTP Client`
- [ ] Build an auth coverage matrix: which endpoints require auth, do the frontend adapters send tokens for each?
- [ ] Check error contract alignment: does the frontend handle the full set of error codes the backend can return?
#### Dimension B: Database & Schema
*Applies to: projects using a relational or document database*
- [ ] Verify all tables have required base columns (`id`, `created_at`, `updated_at`)
- [ ] Check all foreign keys have corresponding indexes
- [ ] If using Supabase or Postgres RLS: verify RLS policies exist on every table storing user data
- [ ] Cross-reference the application's struct/model field names against actual DB column names — flag any drift
- [ ] Check migrations are reversible (up + down) and follow the additive-first strategy
- [ ] Scan storage adapters for N+1 query patterns
#### Dimension C: Configuration & Environment
*Always active*
- [ ] No hardcoded secrets, tokens, URLs, or credentials in source code
- [ ] `.env.template` exists and covers every env var referenced in the codebase
- [ ] Startup validation fails fast on missing required config (does not silently fall back to bad defaults)
- [ ] Secrets are never logged (not in debug, not in error messages)
#### Dimension D: Dependency Health
*Always active*
- [ ] No unused top-level dependencies in `go.mod` / `package.json` / `Cargo.toml`
- [ ] No circular dependencies between feature modules
- [ ] Cross-module imports only use each module's public API (not internal files)
- [ ] Run `npm audit` / `go list -m -json all | nancy` / `cargo audit` — flag high-severity CVEs
#### Dimension E: Test Coverage Gaps
*Always active*
- [ ] A handler/controller test exists for every API endpoint
- [ ] An integration test exists for every storage/database adapter
- [ ] Every error path (catch block, error return) has at least one test that exercises it
- [ ] E2E tests cover the primary user journeys (login, main feature flow, error states)
#### Dimension F: Mobile ↔ Backend
*Applies to: projects with a mobile app and a backend*
- [ ] API version compatibility — mobile must not call endpoints that no longer exist
- [ ] Offline data sync: conflict resolution and retry logic are tested
- [ ] Auth token refresh flows work correctly when the access token expires mid-session
---
### Phase 2: Automated Verification
**Set Mode:** Use `task_boundary` to set mode to **VERIFICATION**
Run the full validation suite (same as `/4-verify`):
1. Linters and static analysis
2. Full test suite
3. Build check
4. Coverage report
### Phase 3: Findings Report
**Output location:** `docs/audits/review-findings-{feature}-{YYYY-MM-DD}-{HHmm}.md`
You MUST save the report to the repo (not just as a conversation artifact) so it can be:
- Referenced from other conversations/agents
- Tracked in version control
- Passed as context to fix workflows
**Steps:**
1. Create the `docs/audits/` directory if it doesn't exist
2. Write the findings report to `docs/audits/review-findings-{feature}-{YYYY-MM-DD}-{HHmm}.md`
3. Use the template below
> **Zero-Findings Guard:** If the audit produces fewer than 3 findings, you MUST complete the "Dimensions Covered" attestation section in the report before declaring a clean result. This proves cross-boundary coverage was not skipped.
```markdown
# Code Audit: {Feature/Module Name}
Date: {date}
## Summary
- **Files reviewed:** N
- **Issues found:** N (X critical, Y major, Z minor)
- **Test coverage:** N%
- **Dimensions activated:** A, B, C, D, E (list which were skipped and why)
## Critical Issues
Issues that must be fixed before deployment.
- [ ] {description} — {file}:{line}
## Major Issues
Issues that should be fixed in the near term.
- [ ] {description} — {file}:{line}
## Minor Issues
Style, naming, or minor improvements.
- [ ] {description} — {file}:{line}
## Verification Results
- Lint: PASS/FAIL
- Tests: PASS/FAIL (N passed, N failed)
- Build: PASS/FAIL
- Coverage: N%
## Dimensions Covered
<!-- Required when total findings < 3 -->
| Dimension | Status | Files / Queries Examined |
|---|---|---|
| A. Integration Contracts | ✅ Checked / ⏭ Skipped (reason) | e.g., all 26 backend routes cross-referenced against 11 frontend adapters |
| B. Database & Schema | ✅ Checked / ⏭ Skipped (reason) | e.g., reviewed all 8 Supabase tables + 4 storage adapters |
| C. Configuration & Environment | ✅ Checked | e.g., scanned for raw secrets, verified .env.template |
| D. Dependency Health | ✅ Checked | e.g., ran npm audit, checked go.mod for unused deps |
| E. Test Coverage Gaps | ✅ Checked | e.g., verified handler tests for all 26 endpoints |
| F. Mobile ↔ Backend | ⏭ Skipped | No mobile app in this project |
```
## Feedback Loop
After the audit produces findings, choose the right workflow based on finding type:
| Finding Type | Example | Workflow |
| ----------------------------------------------------------------------------------- | ------------------------------------ | ------------------------------------- |
| **Nit / minor** (naming, formatting, missing comment) | "Rename `x` to `userCount`" | Fix in this conversation directly |
| **Small isolated fix** (missing log, error handling, validation) | "Add input validation on handler" | `/quick-fix` in a new conversation |
| **Structural change** (wrong abstraction, missing interface, pattern inconsistency) | "Storage not behind interface" | `/refactor` in a new conversation |
| **Missing capability** (new endpoint, feature, auth check) | "No auth middleware on admin routes" | `/orchestrator` in a new conversation |
### Using Findings in Other Contexts
When starting a fix workflow in a new conversation, reference the persisted report:
> "Fix the critical issues in `docs/audits/review-findings-gatekeeper-2026-02-16-1430.md`"
The agent in the new context can read the file directly from the repo — no need to copy-paste findings.
## Completion Criteria
- [ ] All specified files/features reviewed
- [ ] Full verification suite run
- [ ] Findings document saved to `docs/audits/` in the repo
+131
View File
@@ -0,0 +1,131 @@
---
description: End-to-end testing with Playwright MCP
---
# E2E Testing Workflow
## Purpose
Validate complete user journeys through the full system using Playwright MCP.
> **Note:** Paths below follow the project structure defined in `project-structure.md`.
> Adjust paths to match the actual project layout.
## When to Use
- After completing an epic/milestone
- Before release/deploy
- When adding critical user flows
## Prerequisites
- Services running (docker compose up or local dev)
- Feature implementation complete
- Unit and integration tests passing
## Steps
### 1. Start Services
Ensure the full stack is running:
```bash
docker compose up -d
# Wait for health checks
docker compose ps
```
Or local development:
```bash
# Terminal 1: Backend
cd apps/backend && go run cmd/api/main.go
# Terminal 2: Frontend
cd apps/frontend && pnpm run dev
```
### 2. Create E2E Test Plan
Document test cases in `e2e/{feature}-{ui|api}.e2e.test.ts`:
- Happy path flows
- Error handling
- Edge cases
### 3. Execute with Playwright MCP
**Navigate to page:**
```
mcp_playwright_browser_navigate(url="http://localhost:5173/login")
```
**Capture page state:**
```
mcp_playwright_browser_snapshot()
```
**Interact with elements (use refs from snapshot):**
```
mcp_playwright_browser_type(ref="<ref>", text="test@example.com")
mcp_playwright_browser_click(ref="<ref>")
```
**Wait for results:**
```
mcp_playwright_browser_wait_for(text="Welcome")
```
**Capture snapshot for walkthrough documentation:**
```
mcp_playwright_browser_snapshot(filename="docs/e2e-screenshots/{feature}-{description}.md")
```
### 4. Document Results
**Screenshot location:** `docs/e2e-screenshots/{feature}-{description}.png`
1. Create `docs/e2e-screenshots/` if it doesn't exist
2. Save all snapshots there with descriptive names
3. For visual proof (video recording), use `browser_subagent` with `RecordingName` parameter
4. Embed walkthrough documentation in the walkthrough artifact
5. Note any failures or issues
6. Update test files if needed
## E2E Test Structure
```
e2e/ # Single-app projects
├── api/ # (For monorepo: apps/e2e/api/)
│ └── task-crud-api.e2e.test.ts # API-only E2E
└── ui/ # (For monorepo: apps/e2e/ui/)
├── auth-flow-ui.e2e.test.ts # Browser E2E
└── task-flow-ui.e2e.test.ts
```
> Path adapts to project type per `project-structure.md` § Adapting for Different Project Types.
## Example: Auth Flow E2E
```typescript
// e2e/ui/auth-flow-ui.e2e.test.ts
describe('Authentication Flow', () => {
it('should register a new user', async () => {
// Navigate to register
// Fill form
// Submit
// Verify redirect to dashboard
});
it('should login existing user', async () => {
// Navigate to login
// Fill credentials
// Submit
// Verify dashboard loads
});
it('should show error for invalid credentials', async () => {
// Navigate to login
// Fill wrong password
// Submit
// Verify error message
});
});
```
## Completion Criteria
- [ ] All critical user flows tested
- [ ] Snapshots captured and embedded in walkthrough
- [ ] No blocking issues found
+139
View File
@@ -0,0 +1,139 @@
---
description: Build feature workflow orchestrator - chains all phases
---
# Build Feature Workflow
**CRITICAL INSTRUCTION**
YOU ARE FORBIDDEN FROM SKIPPING PHASES.
You must treat this file as a State Machine. You cannot transition to state $N+1$ until state $N$ is completely verified.
## Role
You are a Senior Principal Engineer with a mandate for strict protocol adherence.
Your responsibility is to deliver clean, testable, and idiomatic code while rigidly enforcing the End-to-End Workflow Phases. You must reject any attempt to skip phases—such as writing code without research or completing task/shipping without verification.
Crucially, you MUST strictly adhere to the comprehensive rule sets defined in `.agents/rules/` (e.g., error handling, logging, security, concurrency). These rules are non-negotiable constraints that supersede general training data.
## Pre-Implementation Checklist
Before starting any work, you MUST:
1. Scan `.agents/rules/` directory
2. Identify applicable rules for this task
3. **READ** selected rule files (they are non-negotiable constraints)
## Workflow Phases
Execute phases **sequentially**. Do not skip phases for velocity.
Never deferred quality gates to a later "hardening" phase, defensive rigour early in the cycle trumps.
```mermaid
graph LR
P1[Research] -->|Log Created| P2[Implement]
P2 -->|Unit Tests Pass| P3[Integrate]
P3 -->|Integration Tests Pass| P3b{"E2E needed?"}
P3b -->|Yes| P3c[E2E Test]
P3b -->|No| P4[Verify]
P3c -->|E2E Pass| P4[Verify]
P4 -->|All Linters Pass| P5[Ship]
```
### Phase 1: Research
**File:** `1-research.md`
**Mandatory Rules:** `project-structure.md`, `architectural-pattern.md`
- Analyze request, understand context
- Define scope in `task.md`
- Search external documentation for each technology (use available tools: Qurio, Context7, web search)
- Document findings in `docs/research_logs/{feature}.md`
- If a significant architecture decision is made, create an ADR using the **ADR Skill**
**Skills to consider:**
- **Sequential Thinking** — for complex design decisions requiring iteration
### Phase 2: Implement
**File:** `2-implement.md`
**Mandatory Rules:** `error-handling-principles.md`, `logging-and-observability-mandate.md`, `testing-strategy.md`
- TDD cycle: Red → Green → Refactor
- Unit tests with mocked dependencies
**Skills to consider:**
- **Sequential Thinking** — if refactoring is complex and requires multi-step reasoning
- **Debugging Protocol** — if a failing test has a non-obvious cause
### Phase 3: Integrate
**File:** `3-integrate.md`
**Mandatory Rules:** `testing-strategy.md`, `resources-and-memory-management-principles.md`
- Integration tests with Testcontainers
- Test adapters against real infrastructure
### Phase 3.5: E2E Validation (Conditional)
**File:** `e2e-test.md`
**When Required:**
- UI components were added or modified
- API endpoints were added or modified that interact with frontend
- Critical user-facing flows were changed
**When to Skip:**
- Pure backend/infrastructure changes
- Internal library refactoring
- Test-only changes
**Gate:** At least one critical user journey tested and screenshot captured.
### Phase 4: Verify
**File:** `4-verify.md`
**Mandatory Rules:** `code-completion-mandate.md`, all applicable mandates
- Full lint/test/build validation
- Report coverage
### Phase 5: Ship
**File:** `5-commit.md`
**Mandatory Rules:** `git-workflow-principles.md`
- Git commit with conventional format
- Update task.md
---
## Phase Management
### Task.md Updates
Use this pattern throughout phases:
- `[ ]` = Not started
- `[/]` = In progress (mark when **starting** a task)
- `[x]` = Complete (mark **only after Verify phase passes**)
**Rule:** Never mark `[x]` until Phase 4 (Verify) passes for that task.
### Phase Transitions
Before moving to the next phase, **STOP and verify**:
- [ ] Current phase completion criteria met
- [ ] Outputs created (research log, tests, etc.)
- [ ] No blocking errors
### Error Handling
If a phase fails:
1. **Document the failure** in task summary
2. **Do not proceed** to next phase
3. **Fix the issue** within current phase
4. **Re-run** phase completion criteria
5. Then proceed
### Resuming Work
To resume from a specific phase:
1. Read `task.md` to find current state (`[/]` items)
2. Read the relevant phase file
3. Continue from where you left off
4. No need to re-run completed phases
---
## Quick Reference
| Phase | Output | Blocking |
|-------|--------|----------|
| Research | `task.md` + `docs/research_logs/*.md` | Yes |
| Implement | Unit tests + code | Yes |
| Integrate | Integration tests | Yes (for adapters) |
| E2E (conditional) | E2E tests + screenshots | Yes (when required) |
| Verify | All checks pass | Yes |
| Ship | Git commit | Yes |
+100
View File
@@ -0,0 +1,100 @@
---
description: Profile-driven performance optimization workflow
---
# Performance Optimization Workflow
**Trigger:** User provides profiling data, asks to optimize performance, or benchmarks show regression.
**Skill prerequisite:** Read `.agents/skills/perf-optimization/SKILL.md` before starting.
// turbo-all
## Steps
### 1. Load the Skill
Read the perf-optimization skill and the relevant language module:
```
view_file .agents/skills/perf-optimization/SKILL.md
view_file .agents/skills/perf-optimization/languages/{language}.md
```
### 2. Collect Profile Data
**If the user provides a profile file or URL:**
- Use the language-appropriate extraction script (e.g., `scripts/go-pprof.sh cpu profile.prof`)
**If the user asks to profile from scratch:**
- Run the extraction script in `bench` mode to generate AND analyze profiles in one step:
```bash
bash .agents/skills/perf-optimization/scripts/go-pprof.sh bench ./path/to/package/... BenchmarkName
```
### 3. Analyze
Create a structured analysis document:
```
docs/research_logs/{component}-perf-analysis.md
```
Follow the analysis methodology from SKILL.md:
1. Focus on cumulative, trace flat back to user-land code
2. Identify top 3-5 offenders
3. Separate benchmark artifacts from production cost
4. Identify irreducible floors (reference the language module's table)
### 4. Prioritize Fixes
Create an implementation plan ranking fixes by impact/risk:
- Low risk, high impact → do first
- High risk, any impact → do last or skip
Present the plan to the user for approval before proceeding.
### 5. Implement (one fix at a time)
For each fix, follow the orchestrator workflow:
1. **Write tests first** (TDD Red → Green)
2. **Implement the fix**
3. **Run all existing tests** (`go test -race ./...` or equivalent)
4. **Benchmark immediately** — compare ns/op, B/op, allocs/op
5. **Run quality checks** (formatter, linter, security scanner)
6. **Commit independently** with conventional format: `perf(scope): description`
**Rule:** One fix per commit. Never batch optimizations.
### 6. Final Verification
After all fixes are applied:
1. Run the full benchmark suite with `-count=3` minimum
2. Compare against the original baseline (before any fixes)
3. Run the complete test suite with `-race`
4. Run all quality checks (formatter, linter, security scanner, build)
### 7. Document Results
Update the analysis document with:
- Before/after benchmark comparison table
- Which fixes were applied and which were skipped (and why)
- Any remaining optimization opportunities for future sessions
### 8. Ship
Commit and present the final results to the user with:
- Cumulative benchmark improvement table
- List of commits
- Any follow-up items
---
## Quick Reference
| Phase | Output | Gate |
|---|---|---|
| Profile | Raw data + extracted markdown | Data collected |
| Analyze | `docs/research_logs/{component}-perf-analysis.md` | Top offenders identified |
| Prioritize | Implementation plan | User approved |
| Implement | Tests + code + benchmark per fix | Each fix passes tests |
| Verify | Full benchmark comparison | All checks pass |
| Ship | Conventional commits | User notified |
+58
View File
@@ -0,0 +1,58 @@
---
description: Quick-fix workflow for hotfixes and small bug fixes - skip research, minimal verify
---
# Quick-Fix Workflow
## Purpose
Fast-track bug fixes and small changes that don't require full research or integration phases.
## When to Use
- Bug fixes with a known root cause
- Small, isolated changes (< 50 lines)
- Hotfixes for production issues
- Addressing review findings from `/audit`
## When NOT to Use
- New features (use `/orchestrator`)
- Refactoring (use `/refactor`)
- Changes touching multiple features or modules
## Pre-Implementation Checklist
Before starting, you MUST:
1. Scan `.agents/rules/` directory for applicable rules
2. Read `rule-priority.md` for conflict resolution
3. Confirm the fix scope is truly small and isolated
## Phases
### Phase 1: Diagnose
**Set Mode:** Use `task_boundary` to set mode to **PLANNING**
1. Identify the bug or issue
2. Locate the affected code
3. If the cause is not immediately obvious, activate the **Debugging Protocol** skill
4. Define the fix in `task.md` (1-3 items max)
### Phase 2: Fix + Test (TDD)
**Set Mode:** Use `task_boundary` to set mode to **EXECUTION**
1. **Write a failing test** that reproduces the bug
2. **Apply the minimal fix** to make the test pass
3. **Verify existing tests** still pass
4. Follow applicable rules:
- Error Handling Principles @error-handling-principles.md
- Logging and Observability Mandate @logging-and-observability-mandate.md
### Phase 3: Verify + Ship
**Set Mode:** Use `task_boundary` to set mode to **VERIFICATION**
1. Run the full validation suite (same as `/4-verify`)
2. If all checks pass, commit with conventional format (same as `/5-commit`)
3. Use commit type `fix(<scope>): <description>`
## Completion Criteria
- [ ] Bug reproduced with a test
- [ ] Fix applied and test passes
- [ ] Full verification suite passes
- [ ] Committed with `fix` type
+76
View File
@@ -0,0 +1,76 @@
---
description: Refactor workflow for safely restructuring existing code
---
# Refactor Workflow
## Purpose
Safely restructure existing code while preserving behavior. Uses a different mindset than greenfield feature development: understand first, then change structure.
## When to Use
- Code restructuring (moving, renaming, splitting modules)
- Pattern migration (e.g., switching from callbacks to async/await)
- Dependency upgrades with breaking changes
- Addressing tech debt or architectural improvements
**Requires a specific goal.** Invoke with a clear target, not an open directory:
- ✅ `/refactor extract storage interface in task feature`
- ✅ `/refactor split user handler into separate auth handler`
- ❌ `/refactor apps/backend` (too vague — use `/audit` first to find specific issues)
## When NOT to Use
- New features (use `/orchestrator`)
- Small bug fixes (use `/quick-fix`)
- "Find what to improve" (use `/audit` first, then `/refactor` for structural findings)
## Pre-Implementation Checklist
Before starting, you MUST:
1. Scan `.agents/rules/` directory for applicable rules
2. Read `architectural-pattern.md` and `project-structure.md`
3. Read `rule-priority.md` for conflict resolution
## Phases
### Phase 1: Impact Analysis
**Set Mode:** Use `task_boundary` to set mode to **PLANNING**
1. **Map the blast radius** — what files, modules, and tests are affected?
2. **Document existing behavior** — what tests currently pass? what contracts exist?
3. **Identify risks** — can this be done incrementally or is a big-bang needed?
4. **Create refactoring plan** in `task.md` with incremental steps
5. If decision involves trade-offs, create an ADR using the **ADR Skill**
**Skills to consider:**
- **Sequential Thinking** — for multi-step refactoring with interdependencies
### Phase 2: Incremental Change (TDD)
**Set Mode:** Use `task_boundary` to set mode to **EXECUTION**
For each step in the refactoring plan:
1. **Ensure existing tests pass** before making any change
2. **Make one incremental change** — move, rename, or restructure
3. **Run tests after each change** — behavior must be preserved
4. **Add new tests** if the refactoring exposes untested behavior
5. Follow applicable rules:
- Architectural Patterns @architectural-pattern.md
- Code Organization Principles @code-organization-principles.md (includes circular dependency guidance)
**Key principle:** Never break the build for more than one step at a time.
### Phase 3: Parity Verification
**Set Mode:** Use `task_boundary` to set mode to **VERIFICATION**
1. Run the full validation suite (same as `/4-verify`)
2. **Compare test coverage** — coverage should be equal to or better than before
3. **Verify no behavior changes** — same inputs produce same outputs
4. If applicable, run E2E tests (`/e2e-test`)
### Phase 4: Ship
Follow `/5-commit` with commit type `refactor(<scope>): <description>`
## Completion Criteria
- [ ] Impact analysis documented
- [ ] All changes made incrementally with tests passing at each step
- [ ] Full verification suite passes
- [ ] Test coverage is equal to or better than before
- [ ] Committed with `refactor` type
+1
View File
@@ -0,0 +1 @@
{"timestamp":"2026-04-11T05:27:00Z","iteration":0,"metric":100,"status":"baseline","summary":"Initial baseline - 5/5 tests passing","commit":"baseline"}
+1
View File
@@ -0,0 +1 @@
{"started_at": "2026-04-11T05:27:00Z", "iteration": 0}
+5 -1
View File
@@ -3,7 +3,11 @@
"allow": [
"mcp__plugin_context-mode_context-mode__ctx_batch_execute",
"mcp__plugin_context-mode_context-mode__ctx_execute",
"mcp__plugin_context-mode_context-mode__ctx_search"
"mcp__plugin_context-mode_context-mode__ctx_search",
"Bash(mkdir -p \"d:/Documents/Github/forgelm/.git/worktrees/distracted-pike\")",
"Bash(echo \"D:/Documents/Github/forgelm/.claude/worktrees/distracted-pike/.git\")",
"Bash(echo \"../..\")",
"Bash(echo \"ref: refs/heads/claude/distracted-pike\")"
]
}
}
+76
View File
@@ -0,0 +1,76 @@
# Differential Security & Code Review Report
## 1. Executive Summary
This review covers the last 10 commits focusing on the ForgeLM Tauri React UI, Rust inference engine, and ForgeAgent integrations. The analysis prioritized high-risk areas including inference performance degradation, prompt leakage/injection, UI state management, and authentication/security boundaries.
**Overall Risk Level:** **MEDIUM**
The codebase introduces several defensive mechanisms against prompt leakage and UI state inconsistencies. However, it also introduces aggressive repetition penalties that may severely degrade code generation capabilities, and a prompt leakage regex that is overly broad and prone to false positives.
---
## 2. Detailed Findings by Risk Category
### 2.1 Inference Server & Performance Degradation (HIGH RISK)
**Files Affected:**
- `crates/inference-core/src/sampler.rs`
- `crates/inference-core/src/session.rs`
- `crates/inference-server/src/compat.rs`
- `crates/llama-sys/src/lib.rs`
**Analysis:**
1. **Aggressive Repetition Penalty:** In `crates/inference-server/src/compat.rs`, the `repeat_penalty` has been increased from `1.05` to `1.3`, and `repeat_last_n` set to `512` when no tools are provided.
- *Impact:* A repetition penalty of 1.3 is extremely aggressive for LLMs. While it prevents loops, it will severely degrade the model's ability to generate code, JSON, or any structured text that naturally requires repeating keywords, brackets, or variable names.
- *Recommendation:* Revert to `1.1` - `1.15` for general chat, or make the penalty configurable per-request.
2. **TPS Degradation Monitor is Passive:** `GenerationSession` now includes a TPS degradation monitor (`TpsDegradationConfig`).
- *Impact:* The monitor correctly identifies when TPS drops below 30.0 for 5 seconds, but it only emits a `tracing::warn!`. It does not abort the generation or notify the client, leaving the client hanging on a degraded session.
- *Recommendation:* Expose the degradation state to the server router to allow early termination or client-side warnings via SSE events.
### 2.2 Prompt Injection & Transcript Leakage (MEDIUM RISK)
**Files Affected:**
- `tauri-app/src/lib/chat.ts`
- `crates/inference-server/src/compat.rs`
**Analysis:**
1. **Overly Broad Regex Truncation:** To prevent the model from leaking transcript roles, the frontend introduced `TRANSCRIPT_LEAKAGE_PATTERN = /(?:^|\n)\s*(?:System|User|Assistant|Human):/i;`. The `stripTranscriptLeakage` function aggressively cuts off any text matching this regex.
- *Impact:* This regex will trigger false positives. If a user asks "What is the role of a System Administrator:" or the model generates a markdown list like:
```
Roles:
- User: Performs actions
- System: Manages resources
```
The response will be abruptly truncated at "User:".
- *Recommendation:* Rely strictly on exact backend `stop_sequences` (which was already implemented via `extra_stop_sequences` in `compat.rs`) rather than fuzzy frontend regex truncation, or significantly tighten the regex to expect specific formatting.
2. **Backend Stop Sequences:** The backend correctly injects `\nUser:`, `\nSystem:`, etc., into `extra_stop_sequences` for the `Generic` chat template. This is a secure and standard approach.
### 2.3 UI State Corruption (LOW/MEDIUM RISK)
**Files Affected:**
- `tauri-app/src/features/runtime/useRuntimeManager.ts`
- `tauri-app/src/components/ChatView.tsx`
**Analysis:**
1. **Hidden Side-Effects in State Management:** In `useRuntimeManager.ts`, an auto-scan was added inside `commitSavedSnapshot` via a `useRef` hook (`scanModelsRef.current?.(modelsDir)`).
- *Impact:* While this fixes a stale UI issue, calling an async state-mutating function from within another state reconciliation flow (inside a `.then` block) can lead to race conditions. If the user rapidly toggles settings, the delayed scan could overwrite a newer draft state.
- *Recommendation:* Decouple the model scanning from the snapshot commit, or use a dedicated `useEffect` that watches for `runtimeSnapshot.config.models_dir` changes.
2. **Massive ChatView Refactor:** The redesign to Manus-style tokens (`AgenticToggle`, `StreamingBubble`, etc.) successfully decomposes a monolithic component and appropriately isolates states. The `messageQueue` state is correctly maintained.
### 2.4 Auth & Multi-Agent ForgeAgent Integration (MEDIUM RISK)
**Files Affected:**
- `tauri-app/src-tauri/src/lib.rs`
**Analysis:**
1. **Missing Input Sanitization in Agent Prompts:** The `start_forgeagent_session_command` embeds the raw `prompt` directly into the JSON body sent to the local inference server:
```json
{ "role": "user", "content": &prompt }
```
- *Impact:* While this is a local server, if the ForgeAgent system relies on the model outputting a strict JSON schema (which it parses via `serde_json::from_str`), a prompt injection could force the model to break out of the JSON structure. The code handles this gracefully by falling back to treating the raw text as a summary (`ForgeAgentPlanCompletedPayload { summary: content }`), preventing crashes.
- *Recommendation:* Enforce JSON schema validation natively on the inference server using `response_format: { type: "json_object" }` if supported by the engine, rather than just appending "Limite a cadeia de raciocínio..." to the system prompt.
2. **Duplicate Error Handling Flow:** In `start_forgeagent_session_command`, if the runtime is not ready, it emits an error event via `app_handle.emit` AND returns an `Err("Runtime not ready")`. This may cause the frontend to display duplicate toast notifications or error states.
---
## 3. Summary of Recommendations
1. **Revert Repetition Penalty:** Lower `repeat_penalty` in `compat.rs` from `1.3` to `1.15` to restore coding and structured generation capabilities.
2. **Refine Leakage Prevention:** Remove the broad `TRANSCRIPT_LEAKAGE_PATTERN` regex in `chat.ts` and rely entirely on the exact `stop_sequences` enforced by the backend.
3. **Handle TPS Degradation:** Implement an active response to TPS degradation (e.g., aborting the stream with a specific error chunk) rather than just logging.
4. **Fix Race Conditions:** Move the model auto-scanning logic into a reactive `useEffect` tied to the active models directory rather than an imperative ref call during snapshot commits.
+1
View File
@@ -0,0 +1 @@
|
+14
View File
@@ -0,0 +1,14 @@
# Autoresearch Configuration for Response Quality Optimization
# Target: Improve model response quality and accuracy
target_file = "tauri-app/src/prompts/system.ts"
eval_command = "cd tauri-app && npm test -- --testPathPattern='system-prompt' --json --outputFile=test-results.json && node -e \"const fs=require('fs');const r=JSON.parse(fs.readFileSync('test-results.json'));console.log('Score:', r.success?r.numPassedTests/(r.numPassedTests+r.numFailedTests)*100:0)\""
metric_name = "test_pass_rate"
metric_direction = "higher"
time_budget = "2m"
branch = "autoresearch-quality"
# Alternative eval using benchmark questions
# eval_command = "cd tauri-app && node scripts/benchmark-quality.js"
# metric_name = "quality_score"
# metric_direction = "higher"
+46 -18
View File
@@ -157,19 +157,28 @@ impl InferenceEngine {
bail!("Model file not found: {}", config.model_path.display());
}
let model_name = config
.model_path
.file_name()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_else(|| "<unknown>".into());
info!(
model = %config.model_path.display(),
profile = ?config.profile,
n_gpu_layers = config.n_gpu_layers,
n_ctx = config.n_ctx,
n_batch = config.n_batch,
n_ubatch = config.n_ubatch,
model = %model_name,
profile = ?config.profile,
gpu_layers = config.n_gpu_layers,
n_ctx = config.n_ctx,
n_batch = config.n_batch,
n_ubatch = config.n_ubatch,
flash_attn = config.flash_attn,
use_mmap = config.use_mmap,
"Loading model via llama-sys"
use_mmap = config.use_mmap,
"engine_config"
);
let t_total = std::time::Instant::now();
// 1. Load dynamic library
let t0 = std::time::Instant::now();
let lib = Arc::new(LlamaLib::load_auto().context("Failed to load llama.dll")?);
// 2. Backend init
@@ -177,7 +186,10 @@ impl InferenceEngine {
(lib.backend_load_all)();
(lib.backend_init)();
}
info!("llama backend initialised");
info!(
elapsed_ms = t0.elapsed().as_millis(),
"llama backend initialised"
);
// 3. Model params
let mut mparams = unsafe { (lib.model_default_params)() };
@@ -188,10 +200,12 @@ impl InferenceEngine {
let model_path_c = CString::new(config.model_path.to_str().context("Non-UTF8 model path")?)
.context("CString::new failed")?;
let t_load = std::time::Instant::now();
let model_ptr = unsafe { (lib.model_load_from_file)(model_path_c.as_ptr(), mparams) };
if model_ptr.is_null() {
bail!("llama_model_load_from_file returned NULL – check model path and VRAM");
}
let load_ms = t_load.elapsed().as_millis();
// Log model info
{
@@ -206,11 +220,12 @@ impl InferenceEngine {
let is_hybrid = unsafe { (lib.model_is_hybrid)(model_ptr) };
let is_recurrent = unsafe { (lib.model_is_recurrent)(model_ptr) };
info!(
desc = %desc_str,
params_b = n_params as f64 / 1e9,
desc = %desc_str,
params_b = format!("{:.2}", n_params as f64 / 1e9),
is_hybrid,
is_recurrent,
"Model loaded"
load_ms,
"model_loaded"
);
}
@@ -241,7 +256,11 @@ impl InferenceEngine {
}
let actual_ctx = unsafe { (lib.n_ctx)(ctx_ptr) };
info!(n_ctx = actual_ctx, "Context initialised");
info!(
n_ctx = actual_ctx,
elapsed_ms = t_total.elapsed().as_millis(),
"context_ready"
);
let ctx = OwnedContext {
ptr: ctx_ptr,
@@ -260,7 +279,8 @@ impl InferenceEngine {
///
/// Call once after `load()` before accepting real requests.
pub fn warmup(&mut self) -> Result<()> {
info!("Running engine warmup (dummy decode)…");
let t = std::time::Instant::now();
info!("engine_warmup: starting dummy decode to pre-allocate CUDA buffers");
unsafe {
(self.lib.set_warmup)(self.ctx.ptr, true);
@@ -271,18 +291,18 @@ impl InferenceEngine {
let ret = (self.lib.decode)(self.ctx.ptr, batch);
(self.lib.set_warmup)(self.ctx.ptr, false);
if ret != 0 {
warn!(ret, "Warmup decode returned non-zero (non-fatal)");
warn!(ret, "engine_warmup: decode returned non-zero (non-fatal)");
}
let memory = (self.lib.get_memory)(self.ctx.ptr);
if memory.is_null() {
warn!("llama_get_memory returned NULL during warmup — KV cache not cleared");
warn!("engine_warmup: llama_get_memory returned NULL — KV cache not cleared");
} else {
(self.lib.memory_clear)(memory, false);
}
}
info!("Warmup complete");
info!(elapsed_ms = t.elapsed().as_millis(), "engine_ready");
Ok(())
}
@@ -299,6 +319,14 @@ impl InferenceEngine {
// `true` forces a full clear including M-RoPE position metadata; `false` only clears
// cell occupancy flags and leaves position data intact, which causes the
// "inconsistent sequence positions" error on models that use M-RoPE (e.g. Qwen).
// Truncate prompt for logging — show first 120 chars to aid debugging without flooding logs.
let prompt_preview = if prompt.len() > 120 {
format!("{}…", &prompt[..120])
} else {
prompt.to_owned()
};
tracing::debug!(prompt_chars = prompt.len(), preview = %prompt_preview, "session.new");
unsafe {
let memory = (self.lib.get_memory)(self.ctx.ptr);
if memory.is_null() {
@@ -306,7 +334,7 @@ impl InferenceEngine {
anyhow::bail!("KV cache clear failed: llama_get_memory returned NULL");
} else {
(self.lib.memory_clear)(memory, true);
tracing::info!("engine.kv_cache_cleared");
tracing::debug!("kv_cache_cleared");
}
}
+73 -7
View File
@@ -68,6 +68,10 @@ pub struct GenerationSession {
pub tokens_generated: u32,
pub finished: bool,
// stop detection
recent_output_suffix: String,
max_stop_sequence_len: usize,
// timing
pub prefill_ms: Option<f64>,
gen_start: Option<Instant>,
@@ -203,7 +207,13 @@ impl GenerationSession {
"session.prefill_complete"
);
// create session state
let max_stop_sequence_len = sampler_cfg
.stop_sequences
.iter()
.map(|stop| stop.chars().count())
.max()
.unwrap_or(0);
Ok(Self {
lib,
ctx,
@@ -215,6 +225,8 @@ impl GenerationSession {
max_tokens: sampler_cfg.max_tokens,
tokens_generated: 0,
finished: false,
recent_output_suffix: String::new(),
max_stop_sequence_len,
prefill_ms: Some(prefill_ms),
gen_start: None,
tokens_per_sec: 0.0,
@@ -297,11 +309,16 @@ impl GenerationSession {
// Detokenize
let token_text = self.detokenize_token(new_token);
// Check stop sequences
// (simple check: if generated text so far ends with a stop seq)
// For streaming we check per-token — good enough for Phase 2.
for stop in &self.stop_sequences {
if token_text.trim_end() == stop.as_str() {
// Check stop sequences against the accumulated output suffix so
// fragmented tokens like "\n" + "System" + ":" still match.
if self.max_stop_sequence_len > 0 && !token_text.is_empty() {
push_recent_output_suffix(
&mut self.recent_output_suffix,
&token_text,
self.max_stop_sequence_len,
);
if suffix_matches_any_stop(&self.recent_output_suffix, &self.stop_sequences) {
self.finished = true;
self.finalize_timing();
return None;
@@ -427,6 +444,23 @@ fn build_prefill_plan(token_count: usize, max_batch_tokens: usize) -> Vec<Prefil
plans
}
fn push_recent_output_suffix(current: &mut String, chunk: &str, max_stop_sequence_len: usize) {
if max_stop_sequence_len == 0 || chunk.is_empty() {
return;
}
current.push_str(chunk);
let suffix_chars = current.chars().count();
let keep_chars = max_stop_sequence_len.saturating_mul(2).max(32);
if suffix_chars > keep_chars {
*current = current.chars().skip(suffix_chars - keep_chars).collect();
}
}
fn suffix_matches_any_stop(suffix: &str, stops: &[String]) -> bool {
stops.iter().any(|stop| suffix.ends_with(stop))
}
fn decode_prefill(
lib: &LlamaLib,
ctx: *mut llama_context,
@@ -494,7 +528,10 @@ where
#[cfg(test)]
mod tests {
use super::{build_prefill_plan, decode_token_piece_with, PrefillChunkPlan};
use super::{
build_prefill_plan, decode_token_piece_with, push_recent_output_suffix,
suffix_matches_any_stop, PrefillChunkPlan,
};
#[test]
fn retries_when_piece_buffer_is_too_small() {
@@ -613,6 +650,35 @@ mod tests {
]
);
}
#[test]
fn stop_detection_matches_fragmented_suffix_across_multiple_chunks() {
let stops = vec!["\nSystem:".to_owned()];
let mut suffix = String::new();
let max_len = stops[0].chars().count();
push_recent_output_suffix(&mut suffix, "Resposta util", max_len);
assert!(!suffix_matches_any_stop(&suffix, &stops));
push_recent_output_suffix(&mut suffix, "\n", max_len);
assert!(!suffix_matches_any_stop(&suffix, &stops));
push_recent_output_suffix(&mut suffix, "System", max_len);
assert!(!suffix_matches_any_stop(&suffix, &stops));
push_recent_output_suffix(&mut suffix, ":", max_len);
assert!(suffix_matches_any_stop(&suffix, &stops));
}
#[test]
fn suffix_buffer_is_trimmed_to_a_bounded_window() {
let mut suffix = String::new();
let max_len = "\nAssistant:".chars().count();
push_recent_output_suffix(&mut suffix, &"x".repeat(128), max_len);
assert!(suffix.chars().count() <= max_len.saturating_mul(2).max(32));
}
}
impl Drop for GenerationSession {
+4 -11
View File
@@ -236,13 +236,6 @@ impl LlamaLib {
let lib = unsafe { Library::new(path) }
.with_context(|| format!("Failed to load llama library from {:?}", path))?;
macro_rules! sym {
($name:literal) => {{
*unsafe { lib.get::<unsafe extern "C" fn()>(concat!($name, "\0").as_bytes()) }
.with_context(|| format!("Missing symbol: {}", $name))?
}};
}
// We need transmutes to specific fn types — use a helper macro:
macro_rules! load {
($field:ident, $ty:ty, $name:literal) => {{
@@ -556,11 +549,11 @@ impl LlamaLib {
return Ok(PathBuf::from(name));
}
}
}
anyhow::bail!(
"Cannot find llama shared library. Set LLAMA_DLL_PATH to the full path of llama.dll"
)
anyhow::bail!(
"Cannot find llama shared library. Set LLAMA_DLL_PATH to the full path of llama.dll"
)
}
}
/// Convenience: find + load in one call.
+1906
View File
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,347 @@
# Chat Transcript Leakage + Thinking-Only Fix Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Eliminar o vazamento de transcript/prompt (`# System: ...`), impedir que respostas corrompidas voltem ao contexto e garantir que o chat exiba a resposta útil além do thinking.
**Architecture:** A correção será feita em três camadas complementares: (1) parser/sanitização no frontend para detectar transcript leakage em formatos markdown e role-prefix tradicionais; (2) contenção na persistência e reidratação do histórico para impedir reinfecção do contexto; (3) endurecimento do backend para detectar stops por sufixo acumulado, em vez de comparação token-a-token. O rollout deve ser guiado por testes primeiro, com mudanças pequenas e verificadas por camada.
**Tech Stack:** React + TypeScript + Vitest no frontend; Rust no backend (`inference-server`, `inference-core`); SSE streaming; SQLite/local DB.
---
## Estratégia de implementação
### Abordagem A — Parser defensivo no frontend
Usar uma função única de sanitização que reconheça:
- `System:` / `User:` / `Assistant:` / `Developer:`
- `# System:` / `## System:` etc.
- blocos fenced que comecem com transcript vazado
- prefixos de prompt conhecido (`You are a precise coding assistant running locally...`)
Essa abordagem protege a UI mesmo quando o backend ainda falhar.
### Abordagem B — Quarentena do histórico persistido
Antes de salvar ou reenviar mensagens do assistente:
- limpar transcript leakage
- marcar como corrompida quando só restar prompt echo / transcript
- não reenviar mensagens contaminadas para o modelo
Essa abordagem interrompe o ciclo de reinjeção.
### Abordagem C — Stop detection real no backend
Trocar a verificação de stop sequence baseada em token isolado por verificação baseada em buffer/sufixo acumulado da saída gerada.
Essa abordagem reduz a chance do vazamento nascer.
### Ordem recomendada
1. Frontend parser + testes
2. Histórico/persistência + testes
3. Backend stop detection + testes
4. Validação integrada streaming/non-streaming
---
## Critérios de aceitação
- Respostas contendo `# System:` não aparecem mais como conteúdo visível ao usuário.
- Respostas contendo apenas `<think>...</think>` seguido de transcript leakage não são persistidas como resposta válida.
- Mensagens contaminadas previamente salvas não são reenviadas ao modelo em `buildApiMessages()`.
- O streaming continua mostrando thinking em tempo real, mas a resposta final visível aparece corretamente quando existir.
- O backend corta transcript leakage por stop sequence mesmo quando ele chega fragmentado em múltiplos tokens.
- Testes frontend e backend cobrem os formatos reais do bug.
---
### Task 1: Criar matriz de casos reais do vazamento
**Files:**
- Modify: `tauri-app/src/lib/chat.test.ts`
- Modify: `tauri-app/src/prompts/response-rules.ts`
**Step 1: Escrever testes falhos para transcript leakage em formatos reais**
Adicionar casos para:
- `# System: You are a precise coding assistant...`
- ````md
```markdown
# System: ...
```
````
- `<think>...</think>` seguido de `# System:`
- transcript tradicional `Assistant:` / `User:`
- resposta normal com markdown legítimo que **não** deve ser cortada
**Step 2: Rodar os testes para confirmar falha**
Run: `cd tauri-app && npm test -- src/lib/chat.test.ts`
Expected: FAIL nos novos casos de `# System:` / fenced leakage.
**Step 3: Commit**
```bash
git add tauri-app/src/lib/chat.test.ts tauri-app/src/prompts/response-rules.ts
git commit -m "test: cover transcript leakage variants"
```
---
### Task 2: Centralizar a detecção de transcript leakage no frontend
**Files:**
- Modify: `tauri-app/src/prompts/response-rules.ts`
- Modify: `tauri-app/src/lib/chat.ts`
- Test: `tauri-app/src/lib/chat.test.ts`
**Step 1: Expandir os padrões de transcript leakage**
Em `tauri-app/src/prompts/response-rules.ts`:
- substituir `TRANSCRIPT_LEAKAGE_PATTERN` por versão que aceite prefixos markdown opcionais:
- `(?:^|\n)\s*(?:#{1,6}\s*)?(?:System|User|Assistant|Human|Developer)\s*:`
- adicionar padrão explícito para prompt echo atual:
- `/you are a precise coding assistant running locally/i`
**Step 2: Criar sanitização mais forte em `chat.ts`**
Em `tauri-app/src/lib/chat.ts`:
- extrair helper como `stripPromptEchoAndTranscriptLeakage(text: string): string`
- cortar conteúdo ao encontrar:
- role-prefix simples
- role-prefix markdown (`# System:`)
- prompt echo conhecido no início da seção visível
- garantir que markdown legítimo com `# Heading` continue funcionando se não for um role marker.
**Step 3: Ajustar `isLoopedOrCorruptedResponse()`**
Adicionar o novo padrão de corrupção (`precise coding assistant running locally`).
**Step 4: Rodar os testes**
Run: `cd tauri-app && npm test -- src/lib/chat.test.ts`
Expected: PASS
**Step 5: Commit**
```bash
git add tauri-app/src/prompts/response-rules.ts tauri-app/src/lib/chat.ts tauri-app/src/lib/chat.test.ts
git commit -m "fix: sanitize transcript leakage in assistant responses"
```
---
### Task 3: Impedir reinfecção do histórico em `buildApiMessages()`
**Files:**
- Modify: `tauri-app/src/components/ChatView.tsx`
- Create: `tauri-app/src/components/chat-history-sanitizer.test.ts` (ou incluir no teste existente mais próximo)
**Step 1: Escrever teste falho para reidratação contaminada**
Cobrir cenário:
- mensagem do assistant salva com `# System: ...`
- `buildApiMessages()` não deve reenviar essa parte
- se a mensagem toda for corrupção, ela deve ser descartada do contexto
**Step 2: Extrair helper reutilizável de sanitização do histórico**
Em `ChatView.tsx`:
- substituir regex inline em `buildApiMessages()` por uso do helper central de `chat.ts`
- regra recomendada:
- assistant com conteúdo limpo vazio => descartar do contexto
- assistant com conteúdo útil + vazamento => manter só parte útil
**Step 3: Garantir simetria com persistência**
Não usar regex diferente no histórico. Tudo deve passar pelo mesmo parser/sanitizer.
**Step 4: Rodar testes**
Run: `cd tauri-app && npm test -- src/components/chat-history-sanitizer.test.ts src/components/ChatView.test.tsx`
Expected: PASS
**Step 5: Commit**
```bash
git add tauri-app/src/components/ChatView.tsx tauri-app/src/components/chat-history-sanitizer.test.ts tauri-app/src/components/ChatView.test.tsx
git commit -m "fix: prevent transcript leakage from reentering chat context"
```
---
### Task 4: Corrigir persistência da resposta final vs thinking-only
**Files:**
- Modify: `tauri-app/src/components/ChatView.tsx`
- Modify: `tauri-app/src/components/ChatView.test.tsx`
- Possibly Modify: `tauri-app/src/components/StreamingBubble.tsx`
**Step 1: Escrever testes falhos para dois casos críticos**
Caso A:
- stream = `<think>analise</think>Resposta útil`
- deve salvar `content = "Resposta útil"` e `reasoning_content = "analise"`
Caso B:
- stream = `<think>analise</think># System: ...`
- não deve salvar resposta visível corrompida
- opcionalmente deve mostrar placeholder/erro controlado
**Step 2: Ajustar regra de persistência**
Em `ChatView.tsx`, no trecho final de `send()`:
- calcular `sanitizedVisibleContent`
- se `sanitizedVisibleContent` for vazio **e** reasoning existir, tratar como resposta sem conteúdo útil
- não salvar transcript leak como `content`
- salvar `reasoning_content` apenas quando existir conteúdo útil ou quando a UX desejada permitir thinking-only explícito
**Step 3: Definir comportamento de UX para thinking-only corrompido**
Abordagem recomendada:
- se só sobrar reasoning e o conteúdo visível estiver corrompido/vazio, salvar mensagem de erro controlado:
- `⚠️ O modelo gerou apenas raciocínio interno e nenhuma resposta utilizável.`
- não persistir o prompt echo bruto
**Step 4: Rodar testes**
Run: `cd tauri-app && npm test -- src/components/ChatView.test.tsx`
Expected: PASS
**Step 5: Commit**
```bash
git add tauri-app/src/components/ChatView.tsx tauri-app/src/components/ChatView.test.tsx tauri-app/src/components/StreamingBubble.tsx
git commit -m "fix: handle thinking-only and corrupted final responses"
```
---
### Task 5: Corrigir stop detection no backend
**Files:**
- Modify: `crates/inference-core/src/session.rs`
- Add test in: `crates/inference-core/src/session.rs` or dedicated module test file
**Step 1: Escrever teste falho para stop fragmentado**
Criar teste para simular emissão fragmentada de:
- `\n`
- `System`
- `:`
O detector deve reconhecer que o sufixo acumulado forma `\nSystem:` mesmo quebrado em múltiplos tokens.
**Step 2: Implementar buffer de sufixo acumulado**
Em `session.rs`:
- adicionar campo para acumular os últimos N caracteres emitidos (`recent_output_suffix`)
- a cada token detokenizado:
- concatenar ao buffer
- truncar buffer para tamanho máximo do maior stop + margem pequena
- verificar `ends_with(stop)` no buffer acumulado
**Step 3: Manter comportamento atual de streaming**
Não parar por igualdade de token isolado; parar por sufixo acumulado.
**Step 4: Rodar testes do crate**
Run: `cargo test -p inference-core session -- --nocapture`
Expected: PASS
**Step 5: Commit**
```bash
git add crates/inference-core/src/session.rs
git commit -m "fix: detect stop sequences across fragmented tokens"
```
---
### Task 6: Endurecer stops do template genérico e alinhar com parser
**Files:**
- Modify: `crates/inference-server/src/compat.rs`
- Test: `crates/inference-server/src/compat.rs`
**Step 1: Adicionar variantes markdown aos `extra_stop_sequences`**
Em `prepare_prompt()` para `ChatTemplate::Generic`, incluir:
- `\n# System:`
- `\n# User:`
- `\n# Assistant:`
- `\n## System:` (opcional)
**Step 2: Adicionar teste de contrato**
Garantir que o template genérico injeta essas variantes.
**Step 3: Rodar testes**
Run: `cargo test -p inference-server compat -- --nocapture`
Expected: PASS nos testes do módulo `compat` relevantes.
**Step 4: Commit**
```bash
git add crates/inference-server/src/compat.rs
git commit -m "fix: add markdown transcript stops for generic prompt template"
```
---
### Task 7: Validação integrada frontend + backend
**Files:**
- No code required initially
- Possibly add/update integration tests after manual verification
**Step 1: Rodar suíte frontend completa**
Run: `cd tauri-app && npm test`
Expected: PASS
**Step 2: Rodar build frontend**
Run: `cd tauri-app && npm run build`
Expected: PASS
**Step 3: Rodar testes backend focados**
Run:
```bash
cargo test -p inference-core -- --nocapture
cargo test -p inference-server compat -- --nocapture
```
Expected: PASS nos testes tocados pela mudança.
**Step 4: Verificação manual**
Cenários manuais:
1. resposta normal sem `<think>`
2. resposta com `<think>` e resposta útil
3. resposta com `<think>` + `# System:`
4. mensagem previamente contaminada no DB não deve ser reenviada ao contexto
**Step 5: Commit final**
```bash
git add .
git commit -m "fix: stop transcript leakage from corrupting chat responses"
```
---
## Observações de implementação
- Não criar regex diferentes em cada arquivo. A sanitização precisa ter **uma fonte de verdade**.
- O parser do frontend e a limpeza do histórico devem compartilhar helpers.
- O backend deve reduzir a chance do bug, mas o frontend ainda precisa ser resiliente porque modelos locais podem ignorar formato.
- Não confiar apenas em `isLoopedOrCorruptedResponse()`; usar também sanitização determinística do conteúdo.
## Resultado esperado final
Depois dessas tarefas:
- o usuário não verá mais blocos `# System: ...` nas mensagens;
- o thinking continuará disponível, mas não substituirá a resposta útil;
- respostas corrompidas não contaminarão o histórico;
- o backend interromperá transcript leakage com mais robustez.
@@ -0,0 +1,548 @@
# Memory Architecture: Chat + User Consolidation Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Introduzir uma arquitetura de memória segura e incremental que extraia memórias estruturadas com o modelo local `models/embedding/gemma-3-270m-it-Q8_0.gguf`, separe memória de chat e memória de usuário, consolide memórias entre chats sem duplicação e prepare o terreno para uso dinâmico em prompts futuros.
**Architecture:** A implementação será dividida em duas pipelines desacopladas: (1) extração de memória de chat a partir de mensagens concluídas em um chat específico; (2) consolidação de memória de usuário a partir das memórias de chat já processadas. Para evitar quebrar o fluxo atual, a extração será feita em background, com schema novo e compatibilidade retroativa com `user_memories`. A primeira fase não altera o prompt principal do chat; apenas cria, valida, deduplica e persiste a memória corretamente.
**Tech Stack:** React/TypeScript, Tauri frontend, SQLite local (`db.ts`), API compatível OpenAI do runtime local, modelo GGUF local `gemma-3-270m-it-Q8_0.gguf`, Vitest.
---
## Princípios de segurança desta implementação
1. **Não quebrar o chat atual**: memória nova roda em background e falha silenciosamente.
2. **Não misturar memória bruta com prompt principal ainda**: primeiro persistimos corretamente, depois conectamos ao prompt.
3. **Não usar mensagens inteiras como memória final**: só fatos estruturados, classificados e deduplicados.
4. **Separação explícita**:
- `chat memories` = fatos extraídos de um chat específico
- `user memories` = fatos consolidados a partir de vários chats
5. **Idempotência**: nunca reprocessar o mesmo insumo sem necessidade.
6. **Deduplicação conservadora**: primeiro por chave/categoria/valor normalizado; sem embeddings sofisticados nesta fase.
7. **Modelo único para extração/classificação**: usar `gemma-3-270m-it-Q8_0.gguf` como extractor/classifier local.
---
## Resultado esperado desta fase
Ao final desta implementação:
- cada chat poderá gerar memórias estruturadas próprias;
- memórias de usuário serão derivadas das memórias de chat já consolidadas;
- duplicatas não serão reinseridas;
- memórias terão tipo/classificação/confiança/origem;
- o sistema estará pronto para usar essas memórias em prompts dinâmicos futuros, sem ainda acoplar tudo ao chat principal.
---
## Modelo de dados recomendado
### Tabela 1: `chat_memories`
Representa fatos persistidos derivados de um chat.
Campos sugeridos:
- `id INTEGER PRIMARY KEY`
- `chat_id INTEGER NOT NULL`
- `source_message_id INTEGER NULL`
- `memory_key TEXT NOT NULL`
- `memory_value TEXT NOT NULL`
- `memory_category TEXT NOT NULL`
- `memory_scope TEXT NOT NULL DEFAULT 'chat'`
- `confidence REAL NOT NULL`
- `evidence TEXT NULL`
- `first_seen_at INTEGER NOT NULL`
- `last_seen_at INTEGER NOT NULL`
- `normalized_value TEXT NOT NULL`
- `superseded_by INTEGER NULL`
- `created_at INTEGER NOT NULL`
- `updated_at INTEGER NOT NULL`
Categorias permitidas inicialmente:
- `identity`
- `profile`
- `preference`
- `interest`
- `habit`
- `project`
- `constraint`
- `goal`
- `topic_pattern`
- `skill`
### Tabela 2: `user_memories_v2`
Representa fatos consolidados a partir de `chat_memories`.
Campos sugeridos:
- `id INTEGER PRIMARY KEY`
- `memory_key TEXT NOT NULL`
- `memory_value TEXT NOT NULL`
- `memory_category TEXT NOT NULL`
- `confidence REAL NOT NULL`
- `normalized_value TEXT NOT NULL`
- `supporting_chat_memory_ids TEXT NOT NULL` (JSON array)
- `evidence_summary TEXT NULL`
- `first_seen_at INTEGER NOT NULL`
- `last_seen_at INTEGER NOT NULL`
- `created_at INTEGER NOT NULL`
- `updated_at INTEGER NOT NULL`
### Tabela 3: `memory_jobs`
Fila/log de processamento para idempotência.
Campos sugeridos:
- `id INTEGER PRIMARY KEY`
- `job_type TEXT NOT NULL` (`chat_extract` | `user_consolidate`)
- `target_id INTEGER NOT NULL`
- `status TEXT NOT NULL` (`pending` | `running` | `done` | `failed`)
- `attempts INTEGER NOT NULL DEFAULT 0`
- `last_error TEXT NULL`
- `created_at INTEGER NOT NULL`
- `updated_at INTEGER NOT NULL`
---
## Prompt recomendado para extração de memória de chat
Arquivo novo sugerido: `tauri-app/src/prompts/memory-extraction.ts`
### Prompt base
```text
You are a local memory extraction and classification engine.
Your task is to read a conversation fragment and extract ONLY durable, reusable memory items.
Return ONLY valid JSON as an array.
Each item must follow this schema:
{
"memory_key": "snake_case_key",
"memory_value": "concise factual value",
"memory_category": "identity|profile|preference|interest|habit|project|constraint|goal|topic_pattern|skill",
"confidence": 0.0,
"evidence": "short quote or short rationale",
"is_user_memory_candidate": true
}
Extraction policy:
- Extract durable facts, not temporary requests.
- Extract user preferences, likes/dislikes, recurring interests, profession, age, location, constraints, active projects, goals, skills, and patterns in what the user usually searches or asks about.
- Prefer atomic facts.
- Do not repeat equivalent items.
- If a fact is uncertain, lower confidence.
- If nothing durable exists, return [].
- Never output markdown.
```
### Prompt adicional para consolidação de memória de usuário
```text
You are a user memory consolidator.
Input: existing user memories + candidate chat memories.
Goal: produce ONLY new or improved user-level memories.
Rules:
- Do not repeat existing memories.
- Merge semantically equivalent facts.
- Prefer the most stable formulation.
- Keep only durable cross-chat knowledge.
- If a fact appears in multiple chats, increase confidence.
- Return ONLY valid JSON array with the same schema.
```
---
## Fases de implementação
### Task 1: Congelar o comportamento atual com testes de regressão
**Files:**
- Create: `tauri-app/src/hooks/useMemoryExtractor.test.ts`
- Test: `tauri-app/src/components/ChatView.test.tsx`
**Step 1: Escrever teste de regressão do fluxo atual**
Validar que, se o extractor falhar, o chat principal continua funcionando.
**Step 2: Rodar teste para confirmar baseline**
Run: `cd tauri-app && npm test -- src/hooks/useMemoryExtractor.test.ts`
Expected: FAIL inicialmente (arquivo novo).
**Step 3: Criar teste mínimo de não-regressão**
Cobrir:
- extractor não bloqueia envio de mensagem
- erro de memória não quebra render/chat
**Step 4: Rodar testes**
Run: `cd tauri-app && npm test -- src/hooks/useMemoryExtractor.test.ts src/components/ChatView.test.tsx`
Expected: PASS
**Step 5: Commit**
```bash
git add tauri-app/src/hooks/useMemoryExtractor.test.ts tauri-app/src/components/ChatView.test.tsx
git commit -m "test: add memory extraction regression coverage"
```
---
### Task 2: Introduzir schema novo sem remover o antigo
**Files:**
- Modify: `tauri-app/src/lib/db.ts`
- Modify: `tauri-app/src/lib/types.ts`
- Create: `tauri-app/src/lib/memory-types.ts`
- Test: `tauri-app/src/lib/db.test.ts`
**Step 1: Escrever testes falhos para novas tabelas**
Cobrir criação idempotente de:
- `chat_memories`
- `user_memories_v2`
- `memory_jobs`
**Step 2: Adicionar migração aditiva**
Em `db.ts`, usar `ensureTable`/`ensureColumn` (ou equivalente local) sem remover `user_memories` atual.
**Step 3: Declarar tipos explícitos**
Criar interfaces para:
- `ChatMemory`
- `UserMemoryV2`
- `MemoryJob`
- `MemoryCandidate`
**Step 4: Rodar testes**
Run: `cd tauri-app && npm test -- src/lib/db.test.ts`
Expected: PASS
**Step 5: Commit**
```bash
git add tauri-app/src/lib/db.ts tauri-app/src/lib/types.ts tauri-app/src/lib/memory-types.ts tauri-app/src/lib/db.test.ts
git commit -m "feat: add chat and user memory schema"
```
---
### Task 3: Configurar o modelo local de memória sem tocar no modelo principal
**Files:**
- Create: `tauri-app/src/config/memory.ts`
- Modify: `tauri-app/src/config/ai.ts` or `tauri-app/src/config/ai/index.ts`
- Test: `tauri-app/src/features/runtime/runtime-utils.test.ts`
**Step 1: Escrever teste falho de resolução da configuração**
Configurar constante para:
- `MEMORY_MODEL_PATH = "models/embedding/gemma-3-270m-it-Q8_0.gguf"`
- `MEMORY_MODEL_ID = "gemma-3-270m-it-Q8_0"`
**Step 2: Implementar configuração dedicada**
A memória deve usar modelo separado do chat principal.
**Step 3: Adicionar fallback seguro**
Se o arquivo não existir, a memória desabilita-se sem derrubar o app.
**Step 4: Rodar testes**
Run: `cd tauri-app && npm test -- src/features/runtime/runtime-utils.test.ts`
Expected: PASS
**Step 5: Commit**
```bash
git add tauri-app/src/config/memory.ts tauri-app/src/config/ai.ts tauri-app/src/config/ai/index.ts tauri-app/src/features/runtime/runtime-utils.test.ts
git commit -m "feat: configure dedicated local memory model"
```
---
### Task 4: Implementar prompt e parser de extração estruturada
**Files:**
- Create: `tauri-app/src/prompts/memory-extraction.ts`
- Create: `tauri-app/src/lib/memory-parser.ts`
- Create: `tauri-app/src/lib/memory-parser.test.ts`
**Step 1: Escrever testes falhos para parsing de JSON robusto**
Cobrir:
- array JSON puro
- JSON com ruído ao redor
- array vazio
- item inválido descartado
- normalização de `memory_key`
- clamp de confiança
**Step 2: Implementar parser minimalista**
Funções sugeridas:
- `parseMemoryCandidates(raw: string): MemoryCandidate[]`
- `normalizeMemoryCandidate(candidate): MemoryCandidate | null`
**Step 3: Rodar testes**
Run: `cd tauri-app && npm test -- src/lib/memory-parser.test.ts`
Expected: PASS
**Step 4: Commit**
```bash
git add tauri-app/src/prompts/memory-extraction.ts tauri-app/src/lib/memory-parser.ts tauri-app/src/lib/memory-parser.test.ts
git commit -m "feat: add structured memory extraction prompt and parser"
```
---
### Task 5: Extrair memórias de chat em background
**Files:**
- Modify: `tauri-app/src/hooks/useMemoryExtractor.ts`
- Create: `tauri-app/src/lib/chat-memory-repository.ts`
- Test: `tauri-app/src/hooks/useMemoryExtractor.test.ts`
**Step 1: Escrever testes falhos para pipeline de chat memory**
Cobrir:
- usa modelo de memória dedicado
- envia prompt de extração correto
- salva em `chat_memories`
- marca job/mensagem como processado
- não salva duplicata exata
**Step 2: Refatorar o hook atual**
O hook atual não deve mais gravar direto em `user_memories`.
Agora deve:
- ler mensagens recentes
- chamar o modelo de memória
- parsear candidatos
- persistir em `chat_memories`
- registrar job concluído
**Step 3: Implementar deduplicação conservadora**
Deduplicar por:
- `chat_id`
- `memory_key`
- `normalized_value`
**Step 4: Rodar testes**
Run: `cd tauri-app && npm test -- src/hooks/useMemoryExtractor.test.ts`
Expected: PASS
**Step 5: Commit**
```bash
git add tauri-app/src/hooks/useMemoryExtractor.ts tauri-app/src/lib/chat-memory-repository.ts tauri-app/src/hooks/useMemoryExtractor.test.ts
git commit -m "feat: extract chat-scoped memories with local model"
```
---
### Task 6: Consolidar memórias de usuário a partir das memórias de chat
**Files:**
- Create: `tauri-app/src/lib/user-memory-consolidator.ts`
- Create: `tauri-app/src/lib/user-memory-repository.ts`
- Create: `tauri-app/src/lib/user-memory-consolidator.test.ts`
**Step 1: Escrever testes falhos da consolidação**
Cobrir:
- lê `chat_memories` já processadas
- compara com `user_memories_v2`
- cria apenas memórias novas
- melhora memória existente quando confiança aumenta
- não duplica fatos equivalentes
**Step 2: Implementar consolidador em duas etapas**
Etapa A:
- selecionar candidatas recentes de `chat_memories`
Etapa B:
- chamar o modelo com:
- memórias de usuário já existentes
- novas memórias de chat
- receber apenas novos fatos consolidados
**Step 3: Persistir com rastreabilidade**
Salvar também `supporting_chat_memory_ids`.
**Step 4: Rodar testes**
Run: `cd tauri-app && npm test -- src/lib/user-memory-consolidator.test.ts`
Expected: PASS
**Step 5: Commit**
```bash
git add tauri-app/src/lib/user-memory-consolidator.ts tauri-app/src/lib/user-memory-repository.ts tauri-app/src/lib/user-memory-consolidator.test.ts
git commit -m "feat: consolidate user memories from chat memories"
```
---
### Task 7: Definir política de classificação e uso futuro em prompts
**Files:**
- Create: `tauri-app/src/lib/memory-prompt-context.ts`
- Create: `tauri-app/src/lib/memory-prompt-context.test.ts`
- Modify: `tauri-app/src/hooks/useMemoryExtractor.ts`
**Step 1: Escrever teste falho para construção de contexto dinâmico**
Sem injetar ainda no chat principal, criar builder que monte:
- `chat memory context`
- `user memory context`
separados e filtrados por confiança/categoria.
**Step 2: Implementar builder de contexto**
Regras iniciais:
- `identity/profile/preferences/constraints/goals` com prioridade alta
- `topic_pattern/interests` limitados por recência e confiança
- sem repetir fatos equivalentes
**Step 3: Rodar testes**
Run: `cd tauri-app && npm test -- src/lib/memory-prompt-context.test.ts`
Expected: PASS
**Step 4: Commit**
```bash
git add tauri-app/src/lib/memory-prompt-context.ts tauri-app/src/lib/memory-prompt-context.test.ts tauri-app/src/hooks/useMemoryExtractor.ts
git commit -m "feat: prepare structured memory prompt context builder"
```
---
### Task 8: Adicionar comando de consolidação segura e agendada
**Files:**
- Create: `tauri-app/src/lib/memory-orchestrator.ts`
- Modify: `tauri-app/src/components/ChatView.tsx`
- Test: `tauri-app/src/components/ChatView.test.tsx`
**Step 1: Escrever teste falho do orquestrador**
Cobrir:
- após resposta concluída, roda extração de chat
- consolidação de usuário roda desacoplada/debounced
- falha em qualquer etapa não afeta o chat
**Step 2: Implementar orquestrador leve**
Fluxo:
- `assistant turn complete` → enqueue `chat_extract`
- depois → enqueue `user_consolidate` com debounce maior
**Step 3: Rodar testes**
Run: `cd tauri-app && npm test -- src/components/ChatView.test.tsx`
Expected: PASS
**Step 4: Commit**
```bash
git add tauri-app/src/lib/memory-orchestrator.ts tauri-app/src/components/ChatView.tsx tauri-app/src/components/ChatView.test.tsx
git commit -m "feat: orchestrate chat and user memory processing safely"
```
---
### Task 9: Estratégia de migração sem quebra
**Files:**
- Create: `docs/memory-migration-notes.md`
- Modify: `tauri-app/src/hooks/useMemoryExtractor.ts`
- Test: `tauri-app/src/lib/db.test.ts`
**Step 1: Definir política de coexistência**
- `user_memories` antiga fica preservada
- nova pipeline escreve em `chat_memories` e `user_memories_v2`
- nenhuma leitura crítica do app depende imediatamente da nova tabela
**Step 2: Opcional: migração parcial de legado**
Se necessário, criar utilitário futuro de backfill de `user_memories` → `user_memories_v2`, mas **não nesta fase**.
**Step 3: Documentar explicitamente**
Descrever:
- rollback fácil
- como desligar a feature
- como validar DB localmente
**Step 4: Commit**
```bash
git add docs/memory-migration-notes.md tauri-app/src/hooks/useMemoryExtractor.ts tauri-app/src/lib/db.test.ts
git commit -m "docs: add safe migration notes for new memory architecture"
```
---
### Task 10: Validação final e critérios de aceite
**Files:**
- No new files required
**Step 1: Rodar suíte de memória**
Run:
```bash
cd tauri-app && npm test -- src/hooks/useMemoryExtractor.test.ts src/lib/memory-parser.test.ts src/lib/user-memory-consolidator.test.ts src/lib/memory-prompt-context.test.ts
```
Expected: PASS
**Step 2: Rodar suíte principal**
Run:
```bash
cd tauri-app && npm test
cd tauri-app && npm run build
```
Expected: PASS
**Step 3: Validação manual**
Verificar:
- mensagem normal continua funcionando
- memórias de chat são criadas
- memórias de usuário aparecem só quando novas/mais fortes
- duplicatas não são inseridas
- se modelo de memória falhar, chat continua estável
**Step 4: Commit final**
```bash
git add .
git commit -m "feat: add safe chat and user memory architecture"
```
---
## Riscos e mitigação
### Risco 1: O modelo `gemma-3-270m-it-Q8_0.gguf` não ser bom para extração factual
**Mitigação:**
- manter parser robusto
- limitar escopo a fatos atômicos
- usar confiança + deduplicação conservadora
- tratar esse modelo como extractor heurístico, não como verdade absoluta
### Risco 2: Duplicação excessiva
**Mitigação:**
- chave normalizada
- valor normalizado
- consolidação só com fatos novos ou confiança melhor
### Risco 3: Impacto de performance
**Mitigação:**
- debounce
- background only
- janelas pequenas de contexto
- consolidação de usuário menos frequente que extração de chat
### Risco 4: Quebra do fluxo atual
**Mitigação:**
- schema aditivo
- falhas silenciosas
- nenhuma injeção automática no prompt principal nesta fase
---
## Critérios de aceitação
1. O chat principal continua funcionando sem regressão.
2. O sistema usa o modelo `models/embedding/gemma-3-270m-it-Q8_0.gguf` exclusivamente para a pipeline de memória.
3. Memórias de chat são estruturadas, classificadas e persistidas separadamente.
4. Memórias de usuário são geradas a partir das memórias de chat já processadas.
5. O sistema não duplica memórias equivalentes.
6. Existe builder separado para uso futuro dessas memórias em prompts.
7. Falhas na pipeline de memória não quebram chat, build ou testes.
---
## Recomendação de rollout
### Release 1
- schema novo
- extractor de chat
- consolidator de usuário
- sem injeção no prompt principal
### Release 2
- leitura controlada de `user_memories_v2` + `chat_memories` em prompt dinâmico
- observabilidade de qualidade
### Release 3
- integração com pesquisa web + contexto híbrido
- memória + pesquisa + multiagente no orquestrador final
@@ -0,0 +1,639 @@
# Tauri Physical SQLite Migration Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Migrar o armazenamento atual baseado em `sql.js + localStorage` para um SQLite físico real no Tauri, com melhor performance, durabilidade e estrutura de recuperação de contexto adequada para workloads de IA e memória.
**Architecture:** A migração será feita de forma incremental, introduzindo primeiro uma camada de acesso a dados em Rust/Tauri com SQLite físico como source of truth, enquanto mantemos um caminho de compatibilidade temporária para importação do banco legado do `localStorage`. O frontend não consultará banco diretamente em nenhuma hipótese: toda leitura e escrita passará pelo backend via comandos/APIs Tauri orientados a domínio (`chat`, `message`, `memory`, `migration`, `search`, `summary`), reduzindo acoplamento, melhorando performance e preparando a base para estratégias de compressão/recall que economizam tokens.
**Tech Stack:** Tauri + Rust, SQLite físico local, comandos Tauri (`invoke`), React/TypeScript frontend, migração do legado `sql.js`, Vitest + testes Rust.
---
## Recomendação explícita: caminho ideal
### Escolha recomendada
**Implementar SQLite físico via backend Rust/Tauri como source of truth, com DAO/repositórios tipados e API de domínio para o frontend.**
### Motivo
Esse é o melhor caminho para o seu produto porque oferece:
- **mais performance real** que `sql.js + localStorage`
- **durabilidade de verdade** (arquivo físico)
- **concorrência/controlabilidade melhores**
- **ferramentas reais de inspeção e manutenção** (`sqlite3`, DB Browser for SQLite, LiteCLI, Datasette, SQLx/rusqlite)
- **base ideal para memória, recall, sumarização e deduplicação**, que são as partes que mais ajudam a reduzir tokens em apps de IA
### Escolha técnica recomendada de biblioteca Rust
**Recomendação principal: `rusqlite`**
Motivo:
- simples
- rápido
- excelente para SQLite local embedded
- controle fino sobre pragmas, índices, transações, FTS5
- ideal para Tauri desktop local-first
### Escolha de schema para IA/memória
**Recomendação principal: SQLite relacional + FTS5 + tabelas de memória estruturadas**
Motivo:
- reduz tokens ao permitir:
- recall seletivo por chat
- busca lexical rápida com FTS5
- consolidação e deduplicação de memória
- recuperação por categoria/confiança/recência
- evita mandar histórico bruto inteiro ao modelo
---
## O que muda conceitualmente
### Hoje
- banco em memória (`sql.js`)
- persistência em `localStorage`
- frontend executa SQL diretamente
- schema simples, pouca rastreabilidade para memória
### Depois
- banco físico em arquivo SQLite real
- backend Rust controla schema, migração e consultas
- frontend usa API de domínio, não SQL cru
- schema otimizado para:
- chats
n - mensagens
- memória de chat
- memória de usuário
- FTS para recuperação
- jobs de processamento
- metadados de compressão/contexto
---
## Benefícios esperados para IA e economia de tokens
### 1. Recuperação seletiva
Em vez de reenviar 20+ mensagens sempre, o app poderá consultar:
- últimas mensagens do chat
- memórias de chat com alta confiança
- memórias de usuário relevantes
- snippets recuperados por FTS5
### 2. Context packing mais barato
Você poderá montar prompts com:
- fatos duráveis
- resumo do chat
- poucos trechos relevantes
- não o histórico bruto completo
### 3. Estrutura para compressão e summaries
SQLite físico facilita adicionar depois:
- tabela de `chat_summaries`
- snapshots de contexto
- fragments de recall
- ranking por recência/relevância/confiança
### 4. Ferramentas melhores
Você ganha ferramentas de inspeção e tuning reais:
- `sqlite3`
- DB Browser for SQLite
- `EXPLAIN QUERY PLAN`
- FTS5
- índices compostos
- WAL mode
---
## Arquitetura alvo recomendada
## Camada 1 — Storage engine
Arquivo físico SQLite, por exemplo em:
- diretório de app data do Tauri
- ex.: `ForgeLM/forgelm.sqlite3`
### Pragmas recomendados
- `journal_mode = WAL`
- `synchronous = NORMAL`
- `foreign_keys = ON`
- `temp_store = MEMORY`
- `cache_size` ajustado
Esses pragmas equilibram performance e segurança para desktop local.
## Camada 2 — Backend service layer (source of truth)
Criar módulos Rust como:
- `chat_service.rs`
- `message_service.rs`
- `memory_service.rs`
- `migration_service.rs`
- `search_service.rs`
- `summary_service.rs`
Esses serviços usam repositórios SQLite internamente, mas expõem contratos estáveis para a camada de API.
## Camada 3 — Backend API / Tauri commands
O backend deve servir tudo que o frontend precisar. O frontend não acessa banco, SQL, arquivo SQLite, `sql.js` ou `localStorage` como fonte operacional.
Exemplos de endpoints/commands:
- `chat_list`
- `chat_get`
- `chat_create`
- `chat_rename`
- `chat_delete`
- `message_list_by_chat`
- `message_create_user`
- `message_create_assistant`
- `message_update`
- `message_delete`
- `memory_chat_list`
- `memory_user_list`
- `memory_reprocess_chat`
- `memory_consolidate_user`
- `context_search`
- `summary_get_by_chat`
- `legacy_import`
- `db_health`
## Camada 4 — Frontend API client
No frontend, criar uma camada única de acesso:
- `tauri-app/src/lib/backend-client.ts`
Ela substitui qualquer acesso direto a banco e fala apenas com o backend via `invoke()`.
---
## Schema recomendado
### Tabela `chats`
Manter, com pequenos aprimoramentos.
Campos:
- `id`
- `title`
- `created_at`
- `updated_at`
- `archived_at NULL`
- `summary_updated_at NULL`
### Tabela `messages`
Campos:
- `id`
- `chat_id`
- `role`
- `content`
- `reasoning_content`
- `display_mode`
- `forgeagent_state`
- `prompt_tokens`
- `completion_tokens`
- `total_tokens`
- `tokens_per_second`
- `memory_processed`
- `content_hash`
- `created_at`
### Tabela `chat_memories`
Para memórias derivadas de um chat.
### Tabela `user_memories`
Versão nova consolidada.
### Tabela `memory_jobs`
Controle de processamento.
### Tabela `chat_summaries`
**Recomendação importante:** já criar a tabela, mesmo que não seja usada imediatamente.
Campos sugeridos:
- `id`
- `chat_id`
- `summary_type` (`rolling`, `milestone`, `session`)
- `summary_text`
- `source_message_start_id`
- `source_message_end_id`
- `created_at`
### Tabela virtual FTS5
Recomendação forte:
- `message_search` via FTS5 sobre `messages.content`
- possivelmente FTS também sobre memórias/summaries depois
Isso será um dos maiores ganhos para reduzir tokens no futuro.
---
## Estratégia de migração segura
### Fase 0 — Compatibilidade
Não remover o `sql.js` imediatamente.
### Fase 1 — Criar banco físico e schema novo
- app continua funcionando
- banco físico nasce vazio
### Fase 2 — Implementar importador do legado
- ler `forgelm_db_v2` do `localStorage`
- converter para o schema novo
- importar uma única vez com flag de migração
### Fase 3 — Trocar frontend para `db-client`
- componentes deixam de falar SQL direto
- passam a chamar Tauri commands
### Fase 4 — Desligar `sql.js`
- só depois de validar import, escrita, leitura, edição e build
---
## Plano por fases
### Task 1: Congelar comportamento atual com testes de regressão
**Files:**
- Modify: `tauri-app/src/lib/db.test.ts`
- Modify: `tauri-app/src/App.test.tsx`
- Modify: `tauri-app/src/components/ChatView.test.tsx`
**Step 1: Escrever testes de comportamento atual**
Cobrir:
- criar chat
- carregar histórico
- inserir mensagem user/assistant
- editar/deletar
- persistir métricas
**Step 2: Rodar testes**
Run:
```bash
cd tauri-app && npm test -- src/lib/db.test.ts src/App.test.tsx src/components/ChatView.test.tsx
```
Expected: PASS
**Step 3: Commit**
```bash
git add tauri-app/src/lib/db.test.ts tauri-app/src/App.test.tsx tauri-app/src/components/ChatView.test.tsx
git commit -m "test: freeze current chat db behavior before sqlite migration"
```
---
### Task 2: Introduzir backend SQLite físico no Rust/Tauri
**Files:**
- Create: `src-tauri/src/db/mod.rs`
- Create: `src-tauri/src/db/schema.rs`
- Create: `src-tauri/src/db/connection.rs`
- Modify: `src-tauri/src/main.rs`
- Test: Rust unit tests in `src-tauri/src/db/*`
**Step 1: Escrever testes falhos de inicialização**
Cobrir:
- cria arquivo físico
- aplica schema
- ativa pragmas corretos
**Step 2: Implementar conexão SQLite com `rusqlite`**
- path em diretório de app data do Tauri
- abrir conexão singleton ou pool simples controlado
**Step 3: Aplicar schema inicial**
Criar tabelas `chats`, `messages`, `user_memories`, `chat_memories`, `memory_jobs`, `chat_summaries`, FTS5.
**Step 4: Rodar testes Rust**
Run:
```bash
cargo test db -- --nocapture
```
Expected: PASS
**Step 5: Commit**
```bash
git add src-tauri/src/db src-tauri/src/main.rs
git commit -m "feat: add physical sqlite backend for tauri"
```
---
### Task 3: Implementar repositórios Rust e service layer orientados a domínio
**Files:**
- Create: `src-tauri/src/db/chat_repository.rs`
- Create: `src-tauri/src/db/message_repository.rs`
- Create: `src-tauri/src/db/memory_repository.rs`
- Create: `src-tauri/src/services/chat_service.rs`
- Create: `src-tauri/src/services/message_service.rs`
- Create: `src-tauri/src/services/memory_service.rs`
- Test: Rust tests per repository/service
**Step 1: Escrever testes falhos por repositório e service**
Cobrir:
- CRUD de chats
- insert/list de mensagens
- leitura por chat
- atualização de timestamps
- contratos de service retornando DTOs estáveis
**Step 2: Implementar repositórios mínimos**
Acesso bruto ao SQLite.
**Step 3: Implementar services**
Toda regra de negócio deve ficar aqui. Commands não devem montar SQL.
**Step 4: Rodar testes**
Run:
```bash
cargo test repository -- --nocapture
cargo test service -- --nocapture
```
Expected: PASS
**Step 5: Commit**
```bash
git add src-tauri/src/db/chat_repository.rs src-tauri/src/db/message_repository.rs src-tauri/src/db/memory_repository.rs src-tauri/src/services/chat_service.rs src-tauri/src/services/message_service.rs src-tauri/src/services/memory_service.rs
git commit -m "feat: add sqlite repositories and backend service layer"
```
---
### Task 4: Expor API/backend commands completos para o frontend
**Files:**
- Modify: `src-tauri/src/main.rs`
- Create: `src-tauri/src/commands/chat_commands.rs`
- Create: `src-tauri/src/commands/message_commands.rs`
- Create: `src-tauri/src/commands/memory_commands.rs`
- Create: `src-tauri/src/commands/system_commands.rs`
- Create: `tauri-app/src/lib/backend-client.ts`
- Test: `tauri-app/src/lib/backend-client.test.ts`
**Step 1: Escrever testes falhos do client**
Cobrir contratos como:
- `listChats()`
- `getChat(chatId)`
- `getMessages(chatId)`
- `createChat()`
- `createUserMessage()`
- `createAssistantMessage()`
- `listChatMemories()`
- `listUserMemories()`
- `healthCheck()`
**Step 2: Implementar commands Tauri**
Usar services Rust por trás. O frontend não deve conhecer SQL, schema ou detalhes de armazenamento.
**Step 3: Implementar client TS**
Centralizar chamadas `invoke()` em `backend-client.ts`.
**Step 4: Rodar testes**
Run:
```bash
cd tauri-app && npm test -- src/lib/backend-client.test.ts
```
Expected: PASS
**Step 5: Commit**
```bash
git add src-tauri/src/commands/chat_commands.rs src-tauri/src/commands/message_commands.rs src-tauri/src/commands/memory_commands.rs src-tauri/src/commands/system_commands.rs src-tauri/src/main.rs tauri-app/src/lib/backend-client.ts tauri-app/src/lib/backend-client.test.ts
git commit -m "feat: expose backend api for chats messages memory and system"
```
---
### Task 5: Adaptar frontend para falar apenas com o backend
**Files:**
- Modify: `tauri-app/src/lib/db.ts`
- Modify: `tauri-app/src/App.tsx`
- Modify: `tauri-app/src/components/ChatView.tsx`
- Modify: arquivos que usam `DB` diretamente
**Step 1: Escrever testes falhos de integração**
Cobrir o fluxo principal usando `backend-client`.
**Step 2: Implementar camada de compatibilidade**
Recomendação: não apagar `db.ts` de imediato.
Transformá-lo em façade legada ou adaptador temporário até toda a UI migrar.
**Step 3: Migrar `ChatView`, `App` e demais módulos**
Substituir qualquer leitura/escrita local por chamadas ao backend.
**Step 4: Rodar testes frontend**
Run:
```bash
cd tauri-app && npm test
```
Expected: PASS
**Step 5: Commit**
```bash
git add tauri-app/src/lib/db.ts tauri-app/src/App.tsx tauri-app/src/components/ChatView.tsx
git commit -m "refactor: make frontend consume backend api only"
```
---
### Task 6: Implementar importador do banco legado do localStorage
**Files:**
- Create: `src-tauri/src/db/import_legacy.rs`
- Create: `src-tauri/src/commands/migration_commands.rs`
- Create: `tauri-app/src/lib/legacy-db-export.ts`
- Test: Rust + TS tests
**Step 1: Escrever testes falhos para importação**
Cobrir:
- chats importados
- messages importadas
- user_memories importadas
- import idempotente
**Step 2: Implementar exportador legado no frontend**
Transformar `forgelm_db_v2` em JSON transferível.
**Step 3: Implementar importador no Rust**
Receber JSON e inserir no SQLite físico dentro de transação.
**Step 4: Criar flag de migração concluída**
Pode ser tabela `app_meta` ou arquivo de config.
**Step 5: Rodar testes**
Run:
```bash
cargo test import_legacy -- --nocapture
cd tauri-app && npm test -- src/lib/legacy-db-export.ts
```
Expected: PASS
**Step 6: Commit**
```bash
git add src-tauri/src/db/import_legacy.rs src-tauri/src/commands/migration_commands.rs tauri-app/src/lib/legacy-db-export.ts
git commit -m "feat: import legacy localStorage database into physical sqlite"
```
---
### Task 7: Otimizar SQLite para workloads de IA e economia de tokens
**Files:**
- Modify: `src-tauri/src/db/connection.rs`
- Modify: `src-tauri/src/db/schema.rs`
- Create: `docs/sqlite-context-strategy.md`
- Test: Rust tests/query-plan checks if viable
**Step 1: Ativar FTS5**
Criar tabela virtual para busca textual eficiente em mensagens.
**Step 2: Criar índices úteis**
Sugestões:
- `messages(chat_id, created_at)`
- `messages(role, created_at)`
- `chat_memories(chat_id, memory_category, confidence)`
- `user_memories(memory_category, confidence, updated_at)`
**Step 3: Preparar schema para summaries e recall**
Criar `chat_summaries` e metadados de contexto.
**Step 4: Documentar estratégia anti-token-bloat**
Ex.: usar FTS5 + summary + memory retrieval.
**Step 5: Commit**
```bash
git add src-tauri/src/db/connection.rs src-tauri/src/db/schema.rs docs/sqlite-context-strategy.md
git commit -m "feat: optimize sqlite schema for ai context retrieval"
```
---
### Task 8: Desligar persistência em localStorage e manter fallback controlado
**Files:**
- Modify: `tauri-app/src/lib/db.ts`
- Modify: `tauri-app/src/App.tsx`
- Modify: `tauri-app/src/lib/legacy-db-export.ts`
**Step 1: Escrever testes falhos de pós-migração**
Cobrir:
- app lê do SQLite físico
- localStorage não é mais source of truth
**Step 2: Remover `persist()` como mecanismo principal**
Pode manter apenas como utilitário legado temporário, não mais ativo.
**Step 3: Rodar testes**
Run:
```bash
cd tauri-app && npm test
cd tauri-app && npm run build
```
Expected: PASS
**Step 4: Commit**
```bash
git add tauri-app/src/lib/db.ts tauri-app/src/App.tsx tauri-app/src/lib/legacy-db-export.ts
git commit -m "refactor: disable localStorage db persistence after sqlite migration"
```
---
### Task 9: Validação final ponta a ponta
**Files:**
- No new files required initially
**Step 1: Rodar testes Rust**
```bash
cargo test -- --nocapture
```
**Step 2: Rodar testes frontend**
```bash
cd tauri-app && npm test
cd tauri-app && npm run build
```
**Step 3: Verificação manual**
Validar:
- criar chat
- enviar mensagens
- reiniciar app e confirmar persistência real
- importar banco legado
- editar/deletar mensagens
- inspecionar arquivo SQLite físico com ferramenta externa
**Step 4: Commit final**
```bash
git add .
git commit -m "feat: migrate app storage to physical sqlite in tauri"
```
---
## Ferramentas recomendadas após a migração
Para desenvolvimento e debugging:
- **DB Browser for SQLite** — melhor opção visual simples
- **sqlite3 CLI** — excelente para scripts e inspeção rápida
- **LiteCLI** — autocomplete SQL
- **Datasette** (opcional) — excelente para explorar dados localmente
Para economia de tokens no futuro:
- **FTS5** para recall lexical barato
- **chat_summaries** para compaction
- **chat_memories / user_memories** para fatos duráveis
- **ranking por confiança + recência** para montar prompt enxuto
---
## Riscos e mitigação
### Risco 1: quebrar o frontend ao remover `db.ts`
**Mitigação:** manter adaptador temporário e migrar por etapas.
### Risco 2: migração incompleta do legado
**Mitigação:** importador idempotente + flag de migração + logs claros.
### Risco 3: latência do `invoke()` no Tauri
**Mitigação:** commands orientados a domínio, não excessivamente granulares.
### Risco 4: schema ainda inadequado para IA no futuro
**Mitigação:** já criar FTS5 + summaries + memória estruturada nesta migração.
---
## Critérios de aceitação
1. O app usa SQLite físico real como source of truth.
2. O banco persiste entre reinícios do app sem depender de localStorage.
3. Chats, mensagens e memórias são mantidos corretamente.
4. Existe caminho de importação do banco legado.
5. O frontend não depende mais de SQL direto via `sql.js` para operação normal.
6. O schema já suporta estratégias futuras de redução de tokens.
7. Build e testes seguem verdes.
---
## Resumo executivo
### O que eu recomendo implementar
- **SQLite físico real em Rust/Tauri com `rusqlite`**
- **API de domínio para o frontend**
- **FTS5 + schema preparado para memória e summaries**
- **migração incremental do legado `localStorage`**
### Por que isso é o melhor para IA
Porque essa base permite fazer direito, depois:
- memória estruturada
- busca local eficiente
- resumos incrementais
- recall seletivo
- menos contexto bruto enviado ao modelo
- menor consumo de tokens
---
## Aprovação para execução
Se você aprovar, eu recomendo começar por esta ordem:
1. **Task 1** — congelar comportamento atual
2. **Task 2** — subir o SQLite físico no Tauri
3. **Task 4** — expor commands ao frontend
4. **Task 6** — importação do legado
Essa é a sequência com menor risco.
@@ -0,0 +1,76 @@
# Bug + Dead Code Audit — 2026-04-03
## Scope
Static audit focused on the changed `tauri-app` surface plus verification via:
- `cd tauri-app && npm test`
- `cd tauri-app && npm run build`
- `cd tauri-app/src-tauri && cargo check`
- targeted reference searches with `rg`
## Files reviewed
- `tauri-app/src/App.tsx`
- `tauri-app/src/App.test.tsx`
- `tauri-app/src/components/ChatView.tsx`
- `tauri-app/src/components/Sidebar.tsx`
- `tauri-app/src/components/Sidebar.test.tsx`
- `tauri-app/src/components/MessageBubble.tsx`
- `tauri-app/src/components/StreamingBubble.tsx`
- `tauri-app/src/lib/devLogger.ts`
- `tauri-app/src/types/forgeagent.ts`
- `tauri-app/src/components/chat/index.ts`
- `tauri-app/src/components/chat/MessageBubble.tsx`
- `tauri-app/src/components/chat/ThinkingCard.tsx`
- `tauri-app/src/components/chat/TaskList.tsx`
- `tauri-app/src/components/chat/ArtifactViewer.tsx`
## Findings
### 1. `tauri-app/src/components/ChatView.tsx:21-33`
**Severity:** High
**Problem:** Stream decoders are module-level singletons with `stream: true` and are never flushed/reset.
**Evidence:** `WINDOWS_1252_DECODER` and `UTF8_DECODER` live across requests. If a request aborts or ends mid-multibyte sequence, decoder internal state can bleed into the next response and create phantom mojibake.
**Fix:** Create fresh decoders per request inside `handleSend`, and flush on stream completion with `decoder.decode()`.
### 2. `tauri-app/src/App.tsx:22-25`
**Severity:** Medium
**Problem:** `window.localStorage.getItem(...)` is called without checking that `localStorage` is a functional Storage object.
**Evidence:** Full suite fails with `TypeError: window.localStorage.getItem is not a function` from this exact line.
**Fix:** Guard with `typeof window.localStorage?.getItem === 'function'` and wrap storage access in a safe helper.
### 3. `tauri-app/src/components/Sidebar.tsx:196-198`
**Severity:** Medium
**Problem:** Settings control has only `title="Settings"` and no explicit accessible name.
**Evidence:** Sidebar tests cannot reliably locate the footer settings action after the UI rewrite; button markup lacks `aria-label`.
**Fix:** Add `aria-label="Open settings"`.
### 4. `tauri-app/src/components/ChatView.tsx:467-491`
**Severity:** Medium
**Problem:** Assistant message persistence depends on a DB refresh path after generation; one regression test shows the final reply is not visible after completion.
**Evidence:** `npm test` fails at `src/components/ChatView.test.tsx:514` even though DB insert succeeds (`The answer is 42` logged and inserted).
**Fix:** Add a direct optimistic append to local `messages` state after successful insert, then re-sync from DB.
### 5. `tauri-app/src/components/chat/index.ts:1-4` + `tauri-app/src/components/chat/*`
**Severity:** Medium
**Problem:** Obsolete component subtree appears dead.
**Evidence:** Reference search shows no runtime imports of `components/chat/index.ts`, `ThinkingCard`, `TaskList`, `ArtifactViewer`, or `components/chat/MessageBubble.tsx`.
**Fix:** Remove the unused subtree or wire it back intentionally. Keeping both old and new message-bubble systems increases maintenance risk.
### 6. `tauri-app/src/types/forgeagent.ts:109-112`
**Severity:** Low
**Problem:** `ArtifactViewerProps` is duplicated/unused.
**Evidence:** The real component defines its own local props in `src/components/chat/ArtifactViewer.tsx`; no imports reference the exported type.
**Fix:** Delete the unused interface or centralize the prop type.
## Test / verification summary
- `build`: PASS
- `cargo check`: PASS
- `npm test`: FAIL
- `src/App.test.tsx` (4 failures)
- `src/Sidebar.test.tsx` (4 failures)
- `src/components/ChatView.test.tsx` (1 failure)
## Checklist summary
- Injection/XSS/auth/CSRF/crypto: no concrete new exploit found in reviewed files
- Bugs/regressions: confirmed
- Dead code/obsolete paths: confirmed
- Not fully verified: Rust inference crates outside `tauri-app` changed set were not fully line-audited in this pass
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+53
View File
@@ -0,0 +1,53 @@
# Research Program: Response Quality Optimization
## Goal
Optimize the SYSTEM_PROMPT to maximize response quality and accuracy from local LLMs (Qwen3 0.8B-4B range).
## Current State
- System prompt is minimal (1 sentence)
- Works for basic queries but may lack guidance for:
- Consistent response format
- Appropriate detail level
- Language consistency
- Avoiding hallucinations
## Hypotheses to Test
### 1. Role Clarity
Adding a clearer role definition might improve helpfulness.
- "You are a helpful assistant" → more specific role?
- Test: "You are ForgeLM, a helpful local AI assistant"
### 2. Response Guidelines
Adding lightweight guidance on response style.
- Keep it short to avoid token overhead
- Test adding: "Be concise but thorough"
### 3. Language Consistency
Current: "Respond in the same language as the user"
- Might be too vague for small models
- Test: "Detect the user's language from their message and respond in that language"
### 4. Hallucination Reduction
Adding a gentle reminder to stick to facts.
- Risk: might cause refusals on small models
- Test: "Only state facts you are confident about"
### 5. Instruction Following
Testing if specific formatting helps.
- "Use markdown for code blocks"
- "Bullet points for lists"
## Constraints
- MUST stay under ~50 tokens to preserve context window
- MUST work with 0.8B parameter models
- MUST NOT cause refusal loops
- Positive framing only (no "don't" statements)
## Success Criteria
- Test pass rate > 95%
- No regression in existing functionality
- Maintain compatibility across model sizes
## Past Learnings
(Space to record what worked/didn't work)
+650
View File
@@ -0,0 +1,650 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "forgelm-chat",
"dependencies": {
"@phosphor-icons/react": "^2.1.10",
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-shell": "^2",
"dompurify": "^3.0.0",
"framer-motion": "^12.38.0",
"highlight.js": "^11.0.0",
"lucide-react": "^1.7.0",
"marked": "^12.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
},
"devDependencies": {
"@tauri-apps/cli": "^2",
"@types/dompurify": "^3.0.0",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.27",
"jsdom": "^29.0.1",
"postcss": "^8.5.8",
"tailwindcss": "^3.4.19",
"typescript": "^5.5.3",
"vite": "^5.4.8",
"vitest": "^4.1.2",
},
},
},
"packages": {
"@alloc/quick-lru": ["@alloc/[email protected]", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
"@asamuzakjp/css-color": ["@asamuzakjp/[email protected]", "", { "dependencies": { "@csstools/css-calc": "^3.1.1", "@csstools/css-color-parser": "^4.0.2", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-zd9c/Wdso6v1U7v6w3i/hbAr4K7NaSHImdpvmLt+Y9ea5BhilnIGNkfhOJ7FEIuPipAnE9tZeDOll05WDT0kgg=="],
"@asamuzakjp/dom-selector": ["@asamuzakjp/[email protected]", "", { "dependencies": { "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.2.1", "is-potential-custom-element-name": "^1.0.1" } }, "sha512-r3ElRr7y8ucyN2KdICwGsmj19RoN13CLCa/pvGydghWK6ZzeKQ+TcDjVdtEZz2ElpndM5jXw//B9CEee0mWnVg=="],
"@asamuzakjp/nwsapi": ["@asamuzakjp/[email protected]", "", {}, "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q=="],
"@babel/code-frame": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
"@babel/compat-data": ["@babel/[email protected]", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
"@babel/core": ["@babel/[email protected]", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
"@babel/generator": ["@babel/[email protected]", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
"@babel/helper-compilation-targets": ["@babel/[email protected]", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
"@babel/helper-globals": ["@babel/[email protected]", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
"@babel/helper-module-imports": ["@babel/[email protected]", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="],
"@babel/helper-module-transforms": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
"@babel/helper-plugin-utils": ["@babel/[email protected]", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="],
"@babel/helper-string-parser": ["@babel/[email protected]", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
"@babel/helper-validator-identifier": ["@babel/[email protected]", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@babel/helper-validator-option": ["@babel/[email protected]", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
"@babel/helpers": ["@babel/[email protected]", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
"@babel/parser": ["@babel/[email protected]", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
"@babel/plugin-transform-react-jsx-self": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
"@babel/plugin-transform-react-jsx-source": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
"@babel/template": ["@babel/[email protected]", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
"@babel/traverse": ["@babel/[email protected]", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
"@babel/types": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
"@bramus/specificity": ["@bramus/[email protected]", "", { "dependencies": { "css-tree": "^3.0.0" }, "bin": { "specificity": "bin/cli.js" } }, "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw=="],
"@csstools/color-helpers": ["@csstools/[email protected]", "", {}, "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q=="],
"@csstools/css-calc": ["@csstools/[email protected]", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ=="],
"@csstools/css-color-parser": ["@csstools/[email protected]", "", { "dependencies": { "@csstools/color-helpers": "^6.0.2", "@csstools/css-calc": "^3.1.1" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw=="],
"@csstools/css-parser-algorithms": ["@csstools/[email protected]", "", { "peerDependencies": { "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w=="],
"@csstools/css-syntax-patches-for-csstree": ["@csstools/[email protected]", "", { "peerDependencies": { "css-tree": "^3.2.1" }, "optionalPeers": ["css-tree"] }, "sha512-5GkLzz4prTIpoyeUiIu3iV6CSG3Plo7xRVOFPKI7FVEJ3mZ0A8SwK0XU3Gl7xAkiQ+mDyam+NNp875/C5y+jSA=="],
"@csstools/css-tokenizer": ["@csstools/[email protected]", "", {}, "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA=="],
"@emnapi/core": ["@emnapi/[email protected]", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
"@emnapi/runtime": ["@emnapi/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="],
"@emnapi/wasi-threads": ["@emnapi/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
"@esbuild/aix-ppc64": ["@esbuild/[email protected]", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
"@esbuild/android-arm": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="],
"@esbuild/android-arm64": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="],
"@esbuild/android-x64": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="],
"@esbuild/darwin-arm64": ["@esbuild/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="],
"@esbuild/darwin-x64": ["@esbuild/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="],
"@esbuild/freebsd-arm64": ["@esbuild/[email protected]", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="],
"@esbuild/freebsd-x64": ["@esbuild/[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="],
"@esbuild/linux-arm": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="],
"@esbuild/linux-arm64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="],
"@esbuild/linux-ia32": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="],
"@esbuild/linux-loong64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="],
"@esbuild/linux-mips64el": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="],
"@esbuild/linux-ppc64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="],
"@esbuild/linux-riscv64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="],
"@esbuild/linux-s390x": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="],
"@esbuild/linux-x64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="],
"@esbuild/netbsd-x64": ["@esbuild/[email protected]", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="],
"@esbuild/openbsd-x64": ["@esbuild/[email protected]", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="],
"@esbuild/sunos-x64": ["@esbuild/[email protected]", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="],
"@esbuild/win32-arm64": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="],
"@esbuild/win32-ia32": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="],
"@esbuild/win32-x64": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="],
"@exodus/bytes": ["@exodus/[email protected]", "", { "peerDependencies": { "@noble/hashes": "^1.8.0 || ^2.0.0" }, "optionalPeers": ["@noble/hashes"] }, "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ=="],
"@jridgewell/gen-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
"@jridgewell/resolve-uri": ["@jridgewell/[email protected]", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/[email protected]", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@jridgewell/trace-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@napi-rs/wasm-runtime": ["@napi-rs/[email protected]", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ=="],
"@nodelib/fs.scandir": ["@nodelib/[email protected]", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
"@nodelib/fs.stat": ["@nodelib/[email protected]", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
"@nodelib/fs.walk": ["@nodelib/[email protected]", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
"@oxc-project/types": ["@oxc-project/[email protected]", "", {}, "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg=="],
"@phosphor-icons/react": ["@phosphor-icons/[email protected]", "", { "peerDependencies": { "react": ">= 16.8", "react-dom": ">= 16.8" } }, "sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA=="],
"@rolldown/binding-android-arm64": ["@rolldown/[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA=="],
"@rolldown/binding-darwin-arm64": ["@rolldown/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg=="],
"@rolldown/binding-darwin-x64": ["@rolldown/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw=="],
"@rolldown/binding-freebsd-x64": ["@rolldown/[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw=="],
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA=="],
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w=="],
"@rolldown/binding-linux-arm64-musl": ["@rolldown/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ=="],
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/[email protected]", "", { "os": "linux", "cpu": "ppc64" }, "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ=="],
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/[email protected]", "", { "os": "linux", "cpu": "s390x" }, "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ=="],
"@rolldown/binding-linux-x64-gnu": ["@rolldown/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA=="],
"@rolldown/binding-linux-x64-musl": ["@rolldown/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw=="],
"@rolldown/binding-openharmony-arm64": ["@rolldown/[email protected]", "", { "os": "none", "cpu": "arm64" }, "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg=="],
"@rolldown/binding-wasm32-wasi": ["@rolldown/[email protected]", "", { "dependencies": { "@emnapi/core": "1.9.2", "@emnapi/runtime": "1.9.2", "@napi-rs/wasm-runtime": "^1.1.3" }, "cpu": "none" }, "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q=="],
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA=="],
"@rolldown/binding-win32-x64-msvc": ["@rolldown/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g=="],
"@rolldown/pluginutils": ["@rolldown/[email protected]", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/[email protected]", "", { "os": "android", "cpu": "arm" }, "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA=="],
"@rollup/rollup-android-arm64": ["@rollup/[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA=="],
"@rollup/rollup-darwin-arm64": ["@rollup/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw=="],
"@rollup/rollup-darwin-x64": ["@rollup/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/[email protected]", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w=="],
"@rollup/rollup-freebsd-x64": ["@rollup/[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "s390x" }, "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w=="],
"@rollup/rollup-openbsd-x64": ["@rollup/[email protected]", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/[email protected]", "", { "os": "none", "cpu": "arm64" }, "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/[email protected]", "", { "os": "win32", "cpu": "ia32" }, "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ=="],
"@standard-schema/spec": ["@standard-schema/[email protected]", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@tauri-apps/api": ["@tauri-apps/[email protected]", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="],
"@tauri-apps/cli": ["@tauri-apps/[email protected]", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.10.1", "@tauri-apps/cli-darwin-x64": "2.10.1", "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1", "@tauri-apps/cli-linux-arm64-gnu": "2.10.1", "@tauri-apps/cli-linux-arm64-musl": "2.10.1", "@tauri-apps/cli-linux-riscv64-gnu": "2.10.1", "@tauri-apps/cli-linux-x64-gnu": "2.10.1", "@tauri-apps/cli-linux-x64-musl": "2.10.1", "@tauri-apps/cli-win32-arm64-msvc": "2.10.1", "@tauri-apps/cli-win32-ia32-msvc": "2.10.1", "@tauri-apps/cli-win32-x64-msvc": "2.10.1" }, "bin": { "tauri": "tauri.js" } }, "sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g=="],
"@tauri-apps/cli-darwin-arm64": ["@tauri-apps/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ=="],
"@tauri-apps/cli-darwin-x64": ["@tauri-apps/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw=="],
"@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w=="],
"@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA=="],
"@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg=="],
"@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw=="],
"@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw=="],
"@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ=="],
"@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg=="],
"@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/[email protected]", "", { "os": "win32", "cpu": "ia32" }, "sha512-gXyxgEzsFegmnWywYU5pEBURkcFN/Oo45EAwvZrHMh+zUSEAvO5E8TXsgPADYm31d1u7OQU3O3HsYfVBf2moHw=="],
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg=="],
"@tauri-apps/plugin-shell": ["@tauri-apps/[email protected]", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-jewtULhiQ7lI7+owCKAjc8tYLJr92U16bPOeAa472LHJdgaibLP83NcfAF2e+wkEcA53FxKQAZ7byDzs2eeizg=="],
"@tybys/wasm-util": ["@tybys/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
"@types/babel__core": ["@types/[email protected]", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
"@types/babel__generator": ["@types/[email protected]", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
"@types/babel__template": ["@types/[email protected]", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
"@types/babel__traverse": ["@types/[email protected]", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
"@types/chai": ["@types/[email protected]", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
"@types/deep-eql": ["@types/[email protected]", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
"@types/dompurify": ["@types/[email protected]", "", { "dependencies": { "dompurify": "*" } }, "sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg=="],
"@types/estree": ["@types/[email protected]", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/prop-types": ["@types/[email protected]", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
"@types/react": ["@types/[email protected]", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="],
"@types/react-dom": ["@types/[email protected]", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="],
"@types/trusted-types": ["@types/[email protected]", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
"@vitejs/plugin-react": ["@vitejs/[email protected]", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
"@vitest/expect": ["@vitest/[email protected]", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.4", "@vitest/utils": "4.1.4", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww=="],
"@vitest/mocker": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/spy": "4.1.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg=="],
"@vitest/pretty-format": ["@vitest/[email protected]", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A=="],
"@vitest/runner": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/utils": "4.1.4", "pathe": "^2.0.3" } }, "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ=="],
"@vitest/snapshot": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/pretty-format": "4.1.4", "@vitest/utils": "4.1.4", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw=="],
"@vitest/spy": ["@vitest/[email protected]", "", {}, "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ=="],
"@vitest/utils": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/pretty-format": "4.1.4", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw=="],
"any-promise": ["[email protected]", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
"anymatch": ["[email protected]", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
"arg": ["[email protected]", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
"assertion-error": ["[email protected]", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
"autoprefixer": ["[email protected]", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001774", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA=="],
"baseline-browser-mapping": ["[email protected]", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-HdrkN8eVG2CXxeifv/VdJ4A4RSra1DTW8dc/hdxzhGHN8QePs6gKaWM9pHPcpCoxYZJuOZ8drHmbdpLHjCYjLA=="],
"bidi-js": ["[email protected]", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="],
"binary-extensions": ["[email protected]", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
"braces": ["[email protected]", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"browserslist": ["[email protected]", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
"camelcase-css": ["[email protected]", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="],
"caniuse-lite": ["[email protected]", "", {}, "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg=="],
"chai": ["[email protected]", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
"chokidar": ["[email protected]", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"commander": ["[email protected]", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
"convert-source-map": ["[email protected]", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"css-tree": ["[email protected]", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="],
"cssesc": ["[email protected]", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
"csstype": ["[email protected]", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"data-urls": ["[email protected]", "", { "dependencies": { "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.0" } }, "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA=="],
"debug": ["[email protected]", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"decimal.js": ["[email protected]", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="],
"detect-libc": ["[email protected]", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"didyoumean": ["[email protected]", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="],
"dlv": ["[email protected]", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
"dompurify": ["[email protected]", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA=="],
"electron-to-chromium": ["[email protected]", "", {}, "sha512-q9n5T4BR4Xwa2cwbrwcsDJtHD/enpQ5S1xF1IAtdqf5AAgqDFmR/aakqH3ChFdqd/QXJhS3rnnXFtexU7rax6Q=="],
"entities": ["[email protected]", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
"es-module-lexer": ["[email protected]", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="],
"esbuild": ["[email protected]", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="],
"escalade": ["[email protected]", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"estree-walker": ["[email protected]", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
"expect-type": ["[email protected]", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
"fast-glob": ["[email protected]", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"fastq": ["[email protected]", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
"fdir": ["[email protected]", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fill-range": ["[email protected]", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"fraction.js": ["[email protected]", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="],
"framer-motion": ["[email protected]", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="],
"fsevents": ["[email protected]", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["[email protected]", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"gensync": ["[email protected]", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
"glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
"hasown": ["[email protected]", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"highlight.js": ["[email protected]", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="],
"html-encoding-sniffer": ["[email protected]", "", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="],
"is-binary-path": ["[email protected]", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
"is-core-module": ["[email protected]", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
"is-extglob": ["[email protected]", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-glob": ["[email protected]", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-number": ["[email protected]", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-potential-custom-element-name": ["[email protected]", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="],
"jiti": ["[email protected]", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="],
"js-tokens": ["[email protected]", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"jsdom": ["[email protected]", "", { "dependencies": { "@asamuzakjp/css-color": "^5.1.5", "@asamuzakjp/dom-selector": "^7.0.6", "@bramus/specificity": "^2.4.2", "@csstools/css-syntax-patches-for-csstree": "^1.1.1", "@exodus/bytes": "^1.15.0", "css-tree": "^3.2.1", "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "is-potential-custom-element-name": "^1.0.1", "lru-cache": "^11.2.7", "parse5": "^8.0.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.1", "undici": "^7.24.5", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.1", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.0.0" }, "optionalPeers": ["canvas"] }, "sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w=="],
"jsesc": ["[email protected]", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
"json5": ["[email protected]", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
"lightningcss": ["[email protected]", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
"lightningcss-android-arm64": ["[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
"lightningcss-darwin-arm64": ["[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
"lightningcss-darwin-x64": ["[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
"lightningcss-freebsd-x64": ["[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
"lightningcss-linux-arm-gnueabihf": ["[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
"lightningcss-linux-arm64-gnu": ["[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
"lightningcss-linux-arm64-musl": ["[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
"lightningcss-linux-x64-gnu": ["[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
"lightningcss-linux-x64-musl": ["[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
"lightningcss-win32-arm64-msvc": ["[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
"lightningcss-win32-x64-msvc": ["[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
"lilconfig": ["[email protected]", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
"lines-and-columns": ["[email protected]", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
"loose-envify": ["[email protected]", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
"lru-cache": ["[email protected]", "", {}, "sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ=="],
"lucide-react": ["[email protected]", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-WuvlsjngSk7TnTBJ1hsCy3ql9V9VOdcPkd3PKcSmM34vJD8KG6molxz7m7zbYFgICwsanQWmJ13JlYs4Zp7Arw=="],
"magic-string": ["[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"marked": ["[email protected]", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q=="],
"mdn-data": ["[email protected]", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
"merge2": ["[email protected]", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"micromatch": ["[email protected]", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"motion-dom": ["[email protected]", "", { "dependencies": { "motion-utils": "^12.36.0" } }, "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA=="],
"motion-utils": ["[email protected]", "", {}, "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg=="],
"ms": ["[email protected]", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"mz": ["[email protected]", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
"nanoid": ["[email protected]", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"node-releases": ["[email protected]", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="],
"normalize-path": ["[email protected]", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
"object-assign": ["[email protected]", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"object-hash": ["[email protected]", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="],
"obug": ["[email protected]", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
"parse5": ["[email protected]", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA=="],
"path-parse": ["[email protected]", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
"pathe": ["[email protected]", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"picocolors": ["[email protected]", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["[email protected]", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
"pify": ["[email protected]", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
"pirates": ["[email protected]", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
"postcss": ["[email protected]", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw=="],
"postcss-import": ["[email protected]", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew=="],
"postcss-js": ["[email protected]", "", { "dependencies": { "camelcase-css": "^2.0.1" }, "peerDependencies": { "postcss": "^8.4.21" } }, "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw=="],
"postcss-load-config": ["[email protected]", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="],
"postcss-nested": ["[email protected]", "", { "dependencies": { "postcss-selector-parser": "^6.1.1" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ=="],
"postcss-selector-parser": ["[email protected]", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
"postcss-value-parser": ["[email protected]", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
"punycode": ["[email protected]", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"queue-microtask": ["[email protected]", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"react": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
"react-dom": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="],
"react-refresh": ["[email protected]", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
"read-cache": ["[email protected]", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
"readdirp": ["[email protected]", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"require-from-string": ["[email protected]", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
"resolve": ["[email protected]", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
"reusify": ["[email protected]", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"rolldown": ["[email protected]", "", { "dependencies": { "@oxc-project/types": "=0.124.0", "@rolldown/pluginutils": "1.0.0-rc.15" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.15", "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", "@rolldown/binding-darwin-x64": "1.0.0-rc.15", "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g=="],
"rollup": ["[email protected]", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="],
"run-parallel": ["[email protected]", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"saxes": ["[email protected]", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="],
"scheduler": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
"semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"siginfo": ["[email protected]", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
"source-map-js": ["[email protected]", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"stackback": ["[email protected]", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
"std-env": ["[email protected]", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="],
"sucrase": ["[email protected]", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="],
"supports-preserve-symlinks-flag": ["[email protected]", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
"symbol-tree": ["[email protected]", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="],
"tailwindcss": ["[email protected]", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ=="],
"thenify": ["[email protected]", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
"thenify-all": ["[email protected]", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
"tinybench": ["[email protected]", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
"tinyexec": ["[email protected]", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="],
"tinyglobby": ["[email protected]", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
"tinyrainbow": ["[email protected]", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="],
"tldts": ["[email protected]", "", { "dependencies": { "tldts-core": "^7.0.28" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw=="],
"tldts-core": ["[email protected]", "", {}, "sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ=="],
"to-regex-range": ["[email protected]", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"tough-cookie": ["[email protected]", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw=="],
"tr46": ["[email protected]", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw=="],
"ts-interface-checker": ["[email protected]", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
"tslib": ["[email protected]", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici": ["[email protected]", "", {}, "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ=="],
"update-browserslist-db": ["[email protected]", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
"util-deprecate": ["[email protected]", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"vite": ["[email protected]", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="],
"vitest": ["[email protected]", "", { "dependencies": { "@vitest/expect": "4.1.4", "@vitest/mocker": "4.1.4", "@vitest/pretty-format": "4.1.4", "@vitest/runner": "4.1.4", "@vitest/snapshot": "4.1.4", "@vitest/spy": "4.1.4", "@vitest/utils": "4.1.4", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.4", "@vitest/browser-preview": "4.1.4", "@vitest/browser-webdriverio": "4.1.4", "@vitest/coverage-istanbul": "4.1.4", "@vitest/coverage-v8": "4.1.4", "@vitest/ui": "4.1.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg=="],
"w3c-xmlserializer": ["[email protected]", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="],
"webidl-conversions": ["[email protected]", "", {}, "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ=="],
"whatwg-mimetype": ["[email protected]", "", {}, "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw=="],
"whatwg-url": ["[email protected]", "", { "dependencies": { "@exodus/bytes": "^1.11.0", "tr46": "^6.0.0", "webidl-conversions": "^8.0.1" } }, "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw=="],
"why-is-node-running": ["[email protected]", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
"xml-name-validator": ["[email protected]", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="],
"xmlchars": ["[email protected]", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="],
"yallist": ["[email protected]", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
"@babel/helper-compilation-targets/lru-cache": ["[email protected]", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"anymatch/picomatch": ["[email protected]", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
"chokidar/glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"fast-glob/glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"micromatch/picomatch": ["[email protected]", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
"readdirp/picomatch": ["[email protected]", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
"rolldown/@rolldown/pluginutils": ["@rolldown/[email protected]", "", {}, "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g=="],
"vitest/vite": ["[email protected]", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.15", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw=="],
}
}
@@ -0,0 +1,48 @@
[
"data: {\"id\":\"dbdf573d-036b-440a-8b79-b23dd0834fac\",\"object\":\"chat.completion.chunk\",\"created\":1775246447,\"model\":\"Cooper-Q6_K\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"/com\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"dbdf573d-036b-440a-8b79-b23dd0834fac\",\"object\":\"chat.completion.chunk\",\"created\":1775246447,\"model\":\"Cooper-Q6_K\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"port\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"dbdf573d-036b-440a-8b79-b23dd0834fac\",\"object\":\"chat.completion.chunk\",\"created\":1775246447,\"model\":\"Cooper-Q6_K\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"amentos\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"dbdf573d-036b-440a-8b79-b23dd0834fac\",\"object\":\"chat.completion.chunk\",\"created\":1775246447,\"model\":\"Cooper-Q6_K\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" específicos\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"dbdf573d-036b-440a-8b79-b23dd0834fac\",\"object\":\"chat.completion.chunk\",\"created\":1775246447,\"model\":\"Cooper-Q6_K\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" |\"},\"finish_reason\":null}]}\n\ndata: {\"id",
"data: {\"id\":\"dbdf573d-036b-440a-8b79-b23dd0834fac\",\"object\":\"chat.completion.chunk\",\"created\":1775246447,\"model\":\"Cooper-Q6_K\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" fabricação\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"dbdf573d-036b-440a-8b79-b23dd0834fac\",\"object\":\"chat.completion.chunk\",\"created\":1775246447,\"model\":\"Cooper-Q6_K\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" manual\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"dbdf573d-036b-440a-8b79-b23dd0834fac\",\"object\":\"chat.completion.chunk\",\"created\":1775246447,\"model\":\"Cooper-Q6_K\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" desses\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"dbdf573d-036b-440a-8b79-b23dd0834fac\",\"object\":\"chat.completion.chunk\",\"created\":1775246447,\"model\":\"Cooper-Q6_K\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" itens\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"dbdf573d-036b-440a-8b79-b23dd0834fac\",\"object\":\"chat.completion.chunk\",\"created\":1775246447,\"model\":\"Cooper-Q6_K\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" individual\"},\"finish_reason\":null}]",
"data: {\"id\":\"dbdf573d-036b-440a-8b79-b23dd0834fac\",\"object\":\"chat.completion.chunk\",\"created\":1775246447,\"model\":\"Cooper-Q6_K\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" Hid\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"dbdf573d-036b-440a-8b79-b23dd0834fac\",\"object\":\"chat.completion.chunk\",\"created\":1775246447,\"model\":\"Cooper-Q6_K\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"rául\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"dbdf573d-036b-440a-8b79-b23dd0834fac\",\"object\":\"chat.completion.chunk\",\"created\":1775246447,\"model\":\"Cooper-Q6_K\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"ico\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"dbdf573d-036b-440a-8b79-b23dd0834fac\",\"object\":\"chat.completion.chunk\",\"created\":1775246447,\"model\":\"Cooper-Q6_K\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"/E\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"dbdf573d-036b-440a-8b79-b23dd0834fac\",\"object\":\"chat.completion.chunk\",\"created\":1775246447,\"model\":\"Cooper-Q6_K\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"let\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"dbdf573d-0",
"data: {\"id\":\"dbdf573d-036b-440a-8b79-b23dd0834fac\",\"object\":\"chat.completion.chunk\",\"created\":1775246447,\"model\":\"Cooper-Q6_K\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" -->\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"dbdf573d-036b-440a-8b79-b23dd0834fac\",\"object\":\"chat.completion.chunk\",\"created\":1775246447,\"model\":\"Cooper-Q6_K\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" B\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"dbdf573d-036b-440a-8b79-b23dd0834fac\",\"object\":\"chat.completion.chunk\",\"created\":1775246447,\"model\":\"Cooper-Q6_K\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"{\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"dbdf573d-036b-440a-8b79-b23dd0834fac\",\"object\":\"chat.completion.chunk\",\"created\":1775246447,\"model\":\"Cooper-Q6_K\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"Sele\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"dbdf573d-036b-440a-8b79-b23dd0834fac\",\"object\":\"chat.completion.chunk\",\"created\":1775246447,\"model\":\"Cooper-Q6_K\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"cionar\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"dbdf573d-",
"(\\/\\*\\*(?!\\/))|(\\/\\*)|(\\/\\/)|(\\/(?=[^/\\n]*\\/))|((\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|[a-zA-Z_]\\w*)\\s*=>)|(,)|(\\s+)|(<>)|(<[A-Za-z0-9\\\\._:-]+\\s*\\/>)|(<[A-Za-z0-9\\\\._:-]+)|(\\B|\\b)",
"(\\/\\*\\*(?!\\/))|(\\/\\*)|(\\/\\/)|(\\/(?=[^/\\n]*\\/))|((\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|[a-zA-Z_]\\w*)\\s*=>)|(,)|(\\s+)|(<>)|(<[A-Za-z0-9\\\\._:-]+\\s*\\/>)|(<[A-Za-z0-9\\\\._:-]+)|(\\B|\\b)",
"(\\/\\*\\*(?!\\/))|(/\\*)|(//)|(\\/(?=[^/\\n]*\\/))|((\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|[a-zA-Z_]\\w*)\\s*=>)|(,)|(\\s+)|(<>)|(<[A-Za-z0-9\\\\._:-]+\\s*\\/>)|(<[A-Za-z0-9\\\\._:-]+)|(\\B|\\b)",
"([ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):))|([ ]+((?:I|a|is|so|us|to|at|if|in|it|on|[A-Za-z]+['](d|ve|re|ll|t|s|n)|[A-Za-z]+[-][a-z]+|[A-Za-z][a-z]{2,})[.]?[:]?([.][ ]|[ ])){3})|($)",
"%c[DevLogger]  Chunk #350",
"%c[DevLogger]  Chunk #400",
"%c[DevLogger]  Chunk #450",
"%c[DevLogger]  Chunk #500",
"dbdf573d-036b-440a-8b79-b23dd0834fac",
"dbdf573d-036b-440a-8b79-b23dd0834fac",
"dbdf573d-036b-440a-8b79-b23dd0834fac",
"dbdf573d-036b-440a-8b79-b23dd0834fac",
"2026-04-03T20:00:49.603Z",
"chat.completion.chunk",
"chat.completion.chunk",
"2026-04-03T20:00:49.773Z",
"chat.completion.chunk",
"chat.completion.chunk",
"2026-04-03T20:00:50.102Z",
"2026-04-03T20:00:50.253Z",
"HTMLQuoteElement",
"<blockquote>\n",
"</blockquote>\n",
"<span class=\"",
"isMatchIgnored",
"(')|(\")|(\\])|($)",
"Cooper-Q6_K",
"Cooper-Q6_K",
"language:",
"language-",
"Cooper-Q6_K",
"Cooper-Q6_K",
"get cite",
"set cite",
"mermaid",
"(\\B|\\b)",
"(\\B|\\b)",
"(\\B|\\b)",
"(\\B|\\b)",
"(\\B|\\b)",
"(\\B|\\b)",
"uel"
]
-4676
View File
File diff suppressed because it is too large. Load diff
-1862
View File
File diff suppressed because it is too large. Load diff
-2
View File
@@ -1,2 +0,0 @@
allowBuilds:
esbuild: true
@@ -6,6 +6,12 @@
"permissions": [
"core:default",
"shell:allow-open",
"dialog:allow-open"
"dialog:allow-open",
"core:window:allow-start-dragging",
"core:window:allow-minimize",
"core:window:allow-maximize",
"core:window:allow-unmaximize",
"core:window:allow-close",
"core:window:allow-is-maximized"
]
}
@@ -1 +1 @@
{"default":{"identifier":"default","description":"Default capabilities for ForgeLM Chat","local":true,"windows":["main"],"permissions":["core:default","shell:allow-open","dialog:allow-open"]}}
{"default":{"identifier":"default","description":"Default capabilities for ForgeLM Chat","local":true,"windows":["main"],"permissions":["core:default","shell:allow-open","dialog:allow-open","core:window:allow-start-dragging","core:window:allow-minimize","core:window:allow-maximize","core:window:allow-unmaximize","core:window:allow-close","core:window:allow-is-maximized"]}}
+3 -2
View File
@@ -18,8 +18,9 @@
"minHeight": 600,
"resizable": true,
"fullscreen": false,
"decorations": true,
"transparent": false
"decorations": false,
"transparent": true,
"shadow": true
}
],
"security": {
+9
View File
@@ -3,6 +3,15 @@
height: 100dvh;
overflow: hidden;
position: relative;
/* Solid background here so transparent window still looks right */
background: var(--bg);
border-radius: 10px; /* matches Tauri window radius */
overflow: hidden;
}
/* Offset entire layout below the fixed 48px header */
.app-layout.with-app-header {
padding-top: 48px;
}
.app-main {
+74 -3
View File
@@ -120,6 +120,15 @@ function createMutableDbStub(): DB {
return id;
}
if (sql.includes("INSERT INTO messages (") && sql.includes("'assistant'")) {
const chatId = Number(params?.[0]);
const content = String(params?.[1] ?? "");
const next = rowsByChat.get(chatId) ?? [];
next.push({ id: nextMessageId++, chat_id: chatId, role: "assistant", content, created_at: nextMessageId });
rowsByChat.set(chatId, next);
return nextMessageId - 1;
}
return lastInsertRowId;
}),
query: vi.fn((sql: string, params?: unknown[]) => {
@@ -225,6 +234,8 @@ describe("App chat history", () => {
act(() => root.unmount());
container.remove();
vi.clearAllMocks();
vi.restoreAllMocks();
vi.unstubAllGlobals();
// @ts-expect-error React test environment flag
globalThis.IS_REACT_ACT_ENVIRONMENT = false;
});
@@ -274,7 +285,7 @@ describe("App chat history", () => {
textarea!.dispatchEvent(new Event("input", { bubbles: true }));
});
const sendButton = Array.from(container.querySelectorAll("button")).find((node) => node.textContent?.includes("Send"));
const sendButton = container.querySelector('[aria-label="Send message"]') as HTMLButtonElement | null;
expect(sendButton).toBeTruthy();
await act(async () => {
@@ -345,7 +356,7 @@ describe("App chat history", () => {
root.render(<App />);
});
const newChatButton = Array.from(container.querySelectorAll("button")).find((node) => node.textContent?.includes("New Chat"));
const newChatButton = Array.from(container.querySelectorAll("button")).find((node) => node.textContent?.includes("New task")) as HTMLButtonElement | undefined;
expect(newChatButton).toBeTruthy();
await act(async () => {
@@ -362,7 +373,7 @@ describe("App chat history", () => {
textarea!.dispatchEvent(new Event("input", { bubbles: true }));
});
const sendButton = Array.from(container.querySelectorAll("button")).find((node) => node.textContent?.includes("Send"));
const sendButton = container.querySelector('[aria-label="Send message"]') as HTMLButtonElement | null;
expect(sendButton).toBeTruthy();
await act(async () => {
@@ -410,4 +421,64 @@ describe("App chat history", () => {
expect(container.textContent).toContain("message from sidebar-created chat");
expect(container.textContent).toContain("reply from sidebar-created chat");
});
it("keeps existing chat entries available after creating a new chat and sending the first message", async () => {
const db = createMutableDbStub();
vi.mocked(useDB).mockReturnValue({ db, ready: true });
let resolveFetch: ((value: Response) => void) | null = null;
vi.stubGlobal(
"fetch",
vi.fn(
() =>
new Promise<Response>((resolve) => {
resolveFetch = resolve;
}),
),
);
await act(async () => {
root.render(<App />);
});
const textarea = container.querySelector("textarea") as HTMLTextAreaElement | null;
expect(textarea).toBeTruthy();
const setTextareaValue = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
expect(setTextareaValue).toBeTruthy();
await act(async () => {
setTextareaValue?.call(textarea, "fresh chat keeps sidebar history");
textarea!.dispatchEvent(new Event("input", { bubbles: true }));
});
const sendButton = container.querySelector('[aria-label="Send message"]') as HTMLButtonElement | null;
expect(sendButton).toBeTruthy();
await act(async () => {
sendButton?.click();
});
await act(async () => {
const encoder = new TextEncoder();
resolveFetch?.(
new Response(new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode("data: {\"choices\":[{\"delta\":{\"content\":\"assistant keeps new chat\"}}]}\n"));
controller.enqueue(encoder.encode("data: {\"usage\":{\"prompt_tokens\":2,\"completion_tokens\":2,\"total_tokens\":4}}\n"));
controller.enqueue(encoder.encode("data: [DONE]\n"));
controller.close();
},
}), {
status: 200,
headers: { "Content-Type": "text/event-stream" },
}),
);
await Promise.resolve();
await Promise.resolve();
});
const chatButtons = Array.from(container.querySelectorAll("button")).map((node) => node.textContent ?? "");
expect(chatButtons.some((text) => text.includes("Existing chat"))).toBe(true);
expect(chatButtons.some((text) => text.includes("fresh chat keeps sidebar history"))).toBe(true);
});
});
Loaded 100 of 142 files, more files were not shown because too many files have changed in this diff. Show more