@3lineas/grooveskills 1.1.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: odd-ai
3
+ description: Organic & Outcome-Driven Development (ODD) protocol for AI agents: 7-step organic lifecycle, outcome hypotheses, and verifiable receipts.
4
+ ---
5
+
6
+ # Organic & Outcome-Driven Development (ODD) for AI Agents
7
+
8
+ A hybrid software engineering methodology tailored for AI coding assistants. It combines the lean adaptability of **Organic Driven Development** with the empirical rigor of **Outcome-Driven Development**, eliminating both aimless vibe coding and burdensome waterfall overhead.
9
+
10
+ ---
11
+
12
+ ## 1. The Core Philosophy
13
+
14
+ | Dimension | The Output Trap (Anti-Pattern) | The Outcome Contract (ODD) |
15
+ | :--- | :--- | :--- |
16
+ | **Objective** | Produce diffs and close prompts quickly. | Satisfy a verifiable behavioral hypothesis. |
17
+ | **Planning** | Either zero planning or bureaucratic waterfall. | Proportionate exploration before first write. |
18
+ | **Verification** | Assumes green because the agent says so. | Empirical receipt: tests, typecheck, or runtime proof. |
19
+ | **Memory** | Ephemeral chat context lost on restart. | Persistent records in `.grooveskills/memory/`. |
20
+
21
+ ---
22
+
23
+ ## 2. The 7-Step ODD Lifecycle
24
+
25
+ ```text
26
+ [1. Authorize] -> [2. Explore] -> [3. Resolve] -> [4. Classify] -> [5. Track & Hypothesize] -> [6. Execute] -> [7. Verify Receipt]
27
+ ```
28
+
29
+ ### Step 1: Authorize (Permission Gate)
30
+ - Evaluate whether the user's prompt explicitly authorizes code modification.
31
+ - Inquiries, reviews, audits, or questions trigger **Lane 1 (Answer)**: remain strictly **read-only**.
32
+
33
+ ### Step 2: Explore Proportionately
34
+ - Consult `.grooveskills/memory/context.md` for project architecture, conventions, and key commands.
35
+ - Read only files relevant to the active scope; avoid full codebase traversals that waste tokens.
36
+
37
+ ### Step 3: Resolve Uncertainty Early
38
+ - Disambiguate technical choices directly from codebase inspection.
39
+ - Ask the user focused questions only when facing genuine product or architectural crossroads.
40
+
41
+ ### Step 4: Classify Semantic Scope
42
+ - Classify tasks by semantic complexity and reversibility, never by superficial line counts:
43
+ - **Micro-Fix**: Focused 1-line or localized tweak; execute directly and verify.
44
+ - **Feature / Refactor (Lane 2 - Task)**: Requires state tracking in `.grooveskills/memory/tasks/<slug>.md`.
45
+ - **Architecture (Lane 3 - Spec)**: High ambiguity or cross-domain risk; draft spec and align before writing code.
46
+
47
+ ### Step 5: Track Before First Write & Outcome Hypothesis
48
+ Before modifying source files for substantial tasks, record the **Outcome Hypothesis** in `.grooveskills/memory/tasks/<slug>.md`:
49
+ > *"By [Output / Technical Change], [Outcome / Verifiable Behavior] will occur, proven by [Evidence / Test / Build Command]."*
50
+
51
+ ### Step 6: Execute Proportionate Clean Code
52
+ - Apply strict typing (zero `any`), guard clauses (no nested `if`s), SRP, and file limit (<200 lines).
53
+ - Avoid premature optimizations; implement exactly what the outcome demands.
54
+
55
+ ### Step 7: Verify Against Outcomes (Receipt of Evidence)
56
+ - Run automated verification: typecheck, tests, linter, or build.
57
+ - Record exact outputs under `## Evidence` in the task document.
58
+ - Update `## Next` so work can be resumed seamlessly across agent turns.
59
+
60
+ ---
61
+
62
+ ## 3. Communication Standard
63
+ - Maintain an empathetic, professional, and solution-driven tone.
64
+ - When working in Spanish (`es`), use clear, courteous explanations and actionable guidance.
@@ -0,0 +1,57 @@
1
+ # ODD Protocol & Workflow Examples
2
+
3
+ Concrete patterns and templates for applying Organic & Outcome-Driven Development (ODD) with AI coding agents.
4
+
5
+ ---
6
+
7
+ ## 1. Task Document Template (`.grooveskills/memory/tasks/<slug>.md`)
8
+
9
+ ```markdown
10
+ ---
11
+ status: in-progress
12
+ createdAt: 2026-11-01
13
+ ---
14
+
15
+ # Feature: Strict Valibot Input Sanitization for Auth Route
16
+
17
+ ## Outcome Hypothesis
18
+ By introducing a Valibot validation schema and an isolated Action in \`src/domains/auth/validate-login.ts\`, the authentication endpoint will reject malformed payloads with structured 400 errors and sanitize incoming email strings, proven by passing Vitest unit tests and a zero-error typecheck.
19
+
20
+ ## Scope
21
+ - In scope:
22
+ - Valibot schema for \`loginSchema\` with trimmed, lowercased email.
23
+ - Unit tests covering empty payload, invalid format, and valid login.
24
+ - Out of scope:
25
+ - Database token generation or session cookies.
26
+
27
+ ## Checklist
28
+ - [x] Create \`src/domains/auth/login.schema.ts\` with Valibot schema
29
+ - [x] Implement isolated action in \`src/domains/auth/validate-login-action.ts\`
30
+ - [x] Add test suite in \`src/domains/auth/validate-login.test.ts\`
31
+
32
+ ## Evidence
33
+ - Ran \`pnpm vitest run src/domains/auth/validate-login.test.ts\`: 3 passed (12ms)
34
+ - Ran \`tsc --noEmit\`: Exit code 0 (0 errors)
35
+
36
+ ## Next
37
+ - Wire \`validateLoginAction\` to the Hono route handler.
38
+ ```
39
+
40
+ ---
41
+
42
+ ## 2. Self-Healing Verification Loop (Step 7)
43
+
44
+ When verification fails during Step 7, the agent enters an automatic self-correction loop:
45
+
46
+ 1. **Capture Exact Error**: Read compiler or test failure without guessing.
47
+ 2. **Isolate Root Cause**: Inspect the failing assertion or type mismatch.
48
+ 3. **Remediate Minimally**: Fix the implementation without changing test expectations unless the spec changed.
49
+ 4. **Re-Verify**: Re-run the verification command and log observed proof in `## Evidence`.
50
+
51
+ ---
52
+
53
+ ## 3. ADR Entry Example (`.grooveskills/memory/decisions.md`)
54
+
55
+ ```markdown
56
+ | 2026-11-01 | Adopt ODD for AI Workflow | Standardize on 7-step ODD protocol | Balances organic iteration with empirical outcome verification across all agent sessions. |
57
+ ```
@@ -0,0 +1,113 @@
1
+ ---
2
+ name: python-clean
3
+ description: Modern Python clean code standards, uv environment management, Ruff linting/formatting, Pyright strict type checking, guard clauses (no nested ifs), Pydantic v2 boundaries, zero barrel files (__init__.py re-exports), and hard 200-line limit per file. TRIGGER when writing or modifying Python files (*.py), pyproject.toml, or designing Python services and APIs.
4
+ ---
5
+
6
+ # Python Clean Code Standards (Python 3.12+ / uv / Ruff)
7
+
8
+ Guidelines for writing safe, performant, and maintainable Python code with flat control flow, strict static typing, and decoupled domain architectures.
9
+
10
+ ---
11
+
12
+ ## 1. Golden Rules (Non-negotiable)
13
+
14
+ 1. **Zero Nested `if` Statements — Guard Clauses & Early Returns**:
15
+ - Never nest `if` conditions ("Pyramid of Doom").
16
+ - Validate preconditions, permissions, and edge cases at the top of the function and return or raise immediately.
17
+ - The primary happy path must remain flat and unindented.
18
+
19
+ 2. **Strict Static Typing Everywhere (Zero `Any`)**:
20
+ - Type all function parameters, return types, and class attributes.
21
+ - Use modern union syntax (`str | None`, never `Optional[str]` or `Union`).
22
+ - Use PEP 695 generics (`def first[T](items: list[T]) -> T | None:`).
23
+ - Use `typing.Protocol` for structural typing and dependency inversion instead of heavy abstract base classes.
24
+
25
+ 3. **Strict Boundaries with Pydantic v2**:
26
+ - Never pass loose dictionaries (`dict[str, Any]`) across application layers.
27
+ - Validate external inputs (APIs, CLI arguments, environment configs) using frozen Pydantic models (`ConfigDict(frozen=True)`).
28
+
29
+ 4. **Zero Barrel Files (`__init__.py` Re-exports)**:
30
+ - `__init__.py` files must remain empty or contain only module docstrings.
31
+ - Do not re-export functions or classes via `__all__` or `from .module import *`.
32
+ - Always use explicit, absolute imports from the project package root.
33
+
34
+ 5. **Single Responsibility & 200-Line Limit**:
35
+ - Every file must do exactly one thing. Isolate business actions into single-purpose modules (`app/actions/<domain>/<action>.py`).
36
+ - Strictly adhere to the <200 lines limit per file. Split helpers into atomic modules.
37
+
38
+ 6. **Friendly & Persuasive User Copy**:
39
+ - Error messages, validations, and logs intended for users must be empathetic, clear, and solution-focused.
40
+
41
+ ---
42
+
43
+ ## 2. Guard Clauses vs Nested Ifs
44
+
45
+ ```python
46
+ # ❌ FORBIDDEN: Deeply nested if statements
47
+ def activate_subscription(customer_id: str | None, plan: Plan | None) -> Invoice:
48
+ if customer_id is not None:
49
+ if plan is not None:
50
+ if plan.is_active:
51
+ return execute_activation(customer_id, plan)
52
+ raise ValueError("Invalid parameters")
53
+
54
+ # ✅ CLEAN: Guard clauses with early returns and clear errors
55
+ def activate_subscription(customer_id: str | None, plan: Plan | None) -> Invoice:
56
+ if customer_id is None or not customer_id.strip():
57
+ raise AppError(
58
+ message="El identificador de cliente es requerido para continuar.",
59
+ status_code=400,
60
+ )
61
+
62
+ if plan is None or not plan.is_active:
63
+ raise AppError(
64
+ message="El plan seleccionado no está disponible actualmente. ¿Deseas elegir otro?",
65
+ status_code=422,
66
+ )
67
+
68
+ return execute_activation(customer_id, plan)
69
+ ```
70
+
71
+ ---
72
+
73
+ ## 3. Atomic Action Blueprint
74
+
75
+ ```python
76
+ from dataclasses import dataclass
77
+ from app.domains.billing.models import Invoice, SubscriptionPlan
78
+ from app.errors.app_error import AppError
79
+ from app.types.response import ActionResponse
80
+
81
+ @dataclass(frozen=True, slots=True)
82
+ class ChargeSubscriptionInput:
83
+ customer_id: str
84
+ plan_id: str
85
+ amount_cents: int
86
+
87
+ def charge_subscription_action(
88
+ input_data: ChargeSubscriptionInput,
89
+ payment_gateway: PaymentGatewayProtocol,
90
+ ) -> ActionResponse[Invoice]:
91
+ if not input_data.customer_id.strip():
92
+ raise AppError(
93
+ message="El cliente es requerido para procesar el pago.",
94
+ status_code=400,
95
+ )
96
+
97
+ if input_data.amount_cents <= 0:
98
+ raise AppError(
99
+ message="El monto de la suscripción debe ser superior a cero.",
100
+ status_code=422,
101
+ )
102
+
103
+ invoice = payment_gateway.charge(input_data.customer_id, input_data.amount_cents)
104
+ return ActionResponse(success=True, data=invoice)
105
+ ```
106
+
107
+ ---
108
+
109
+ ## 4. Sibling Skills & References
110
+
111
+ - **`best-practices`**: Universal clean architecture rules.
112
+ - **`ux-writing`**: Persuasive and courteous microcopy guidelines.
113
+ - Tooling setup & Pydantic templates: `references/python-tooling-and-types.md`.
@@ -0,0 +1,78 @@
1
+ # Python Tooling & Architecture Reference
2
+
3
+ ## 1. Modern Tooling Baseline (`pyproject.toml`)
4
+
5
+ Modern Python projects standardize on **uv**, **Ruff**, and **Pyright**:
6
+
7
+ ```toml
8
+ [project]
9
+ name = "my-service"
10
+ version = "0.1.0"
11
+ requires-python = ">=3.12"
12
+ dependencies = [
13
+ "pydantic>=2.7.0",
14
+ ]
15
+
16
+ [tool.ruff]
17
+ line-length = 88
18
+ target-version = "py312"
19
+
20
+ [tool.ruff.lint]
21
+ select = [
22
+ "E", # pycodestyle errors
23
+ "F", # pyflakes
24
+ "I", # isort imports
25
+ "UP", # pyupgrade modern syntax
26
+ "B", # bugbear
27
+ "SIM", # simplify (detects nested ifs & unnecessary else)
28
+ "RET", # return early checks
29
+ ]
30
+
31
+ [tool.pyright]
32
+ typeCheckingMode = "strict"
33
+ reportMissingTypeStubs = false
34
+ pythonVersion = "3.12"
35
+ ```
36
+
37
+ ---
38
+
39
+ ## 2. Structured AppError & Response Pattern
40
+
41
+ ```python
42
+ from dataclasses import dataclass
43
+
44
+ class AppError(Exception):
45
+ def __init__(self, message: str, status_code: int = 400, code: str = "BAD_REQUEST") -> None:
46
+ super().__init__(message)
47
+ self.message = message
48
+ self.status_code = status_code
49
+ self.code = code
50
+
51
+ @dataclass(frozen=True, slots=True)
52
+ class ActionResponse[T]:
53
+ success: bool
54
+ data: T | None = None
55
+ error: str | None = None
56
+ ```
57
+
58
+ ---
59
+
60
+ ## 3. Directory Layout (No `__init__.py` Barrels)
61
+
62
+ ```text
63
+ src/
64
+ └── app/
65
+ ├── errors/
66
+ │ └── app_error.py
67
+ ├── actions/
68
+ │ └── billing/
69
+ │ ├── charge_subscription.py # One action per file (<100 lines)
70
+ │ └── cancel_subscription.py
71
+ ├── domains/
72
+ │ └── billing/
73
+ │ ├── models.py # Pure frozen dataclasses
74
+ │ └── tax_calculator.py # Pure function
75
+ └── infrastructure/
76
+ └── stripe/
77
+ └── client.py
78
+ ```
@@ -0,0 +1,119 @@
1
+ ---
2
+ name: rust-clean
3
+ description: Modern Rust clean code standards, let-else guard clauses, no nested ifs/matches, explicit typed error handling with thiserror, Clippy strictness, zero unwrap in production, Newtype pattern, zero barrel files (mod.rs), and hard 200-line limit per file. TRIGGER when writing or modifying Rust files (*.rs), Cargo.toml, or designing Rust domain modules and traits.
4
+ ---
5
+
6
+ # Rust Clean Code Standards (Rust 2024 / 1.80+)
7
+
8
+ Engineering guidelines for writing idiomatic, highly performant, and maintainable Rust code with flat control flow, strict error boundaries, and zero runtime panics.
9
+
10
+ ---
11
+
12
+ ## 1. Golden Rules (Non-negotiable)
13
+
14
+ 1. **Zero Nested `if` / `match` — Guard Clauses with `let-else`**:
15
+ - Never nest `if let` or deep `match` pyramids.
16
+ - Use `let-else` statements at function entry points to bind irrefutable patterns and exit immediately via divergence (`return`, `break`).
17
+ - The primary happy path must always stay flat and left-aligned.
18
+
19
+ 2. **Zero `.unwrap()` or `.expect()` in Production Code**:
20
+ - Production logic must never panic on dynamic runtime data.
21
+ - Propagate errors explicitly with the `?` operator or handle `None`/`Err` via `let-else`.
22
+
23
+ 3. **Explicit Domain Errors with `thiserror`**:
24
+ - Model all domain and infrastructure failures as strongly typed enums derived with `thiserror::Error`.
25
+ - Distinguish technical context for logging (`tracing`) from courteous, solution-oriented error messages presented to end users.
26
+
27
+ 4. **Make Illegal States Unrepresentable (Newtypes & ADTs)**:
28
+ - Use the **Newtype pattern** (`pub struct UserId(pub String);`) to prevent passing arbitrary primitives interchangeably.
29
+ - Replace boolean flag combinations with algebraic state enums.
30
+
31
+ 5. **Zero Barrel Files (`mod.rs`) & Explicit Absolute Imports (`crate::...`)**:
32
+ - Do not use old-style `mod.rs` files filled with wildcard re-exports (`pub use something::*`).
33
+ - Use the modern file hierarchy (`domain.rs` and `domain/module.rs`).
34
+ - Always import symbols directly from `crate::...` path roots. Avoid deep relative paths (`super::super::`).
35
+
36
+ 6. **Isolated Actions & 200-Line Limit**:
37
+ - Isolate each business use case into its own atomic action file (`src/actions/<domain>/<action>.rs`).
38
+ - Keep all source files strictly under 200 lines. Split large modules into atomic domain helpers.
39
+
40
+ 7. **Zero Allocation Waste**:
41
+ - Prefer borrowed views (`&str`, `&[T]`) over cloning (`.clone()`) unless transfer of ownership is explicitly required.
42
+
43
+ ---
44
+
45
+ ## 2. Canonical Guard Clause Pattern (`let-else`)
46
+
47
+ ```rust
48
+ // ❌ FORBIDDEN: Nested match / if-let pyramid
49
+ pub fn process_order(order: Option<OrderInput>) -> Result<Order, AppError> {
50
+ if let Some(input) = order {
51
+ if input.is_valid() {
52
+ if input.items.len() > 0 {
53
+ return Ok(execute(input));
54
+ }
55
+ }
56
+ }
57
+ Err(AppError::bad_request("Invalid order payload"))
58
+ }
59
+
60
+ // ✅ CLEAN: Flat guard clauses with let-else
61
+ pub fn process_order(order: Option<OrderInput>) -> Result<Order, AppError> {
62
+ let Some(input) = order else {
63
+ return Err(AppError::validation(
64
+ "Por favor, ingresa los datos de tu orden para continuar.",
65
+ ));
66
+ };
67
+
68
+ if !input.is_valid() || input.items.is_empty() {
69
+ return Err(AppError::validation(
70
+ "Tu orden debe contener al menos un producto válido.",
71
+ ));
72
+ }
73
+
74
+ Ok(execute(input))
75
+ }
76
+ ```
77
+
78
+ ---
79
+
80
+ ## 3. Atomic Action Blueprint
81
+
82
+ ```rust
83
+ use crate::domains::billing::models::{Customer, Invoice};
84
+ use crate::errors::AppError;
85
+
86
+ pub struct ChargeSubscriptionInput {
87
+ pub customer_id: String,
88
+ pub plan_id: String,
89
+ pub amount_cents: u64,
90
+ }
91
+
92
+ pub fn charge_subscription_action(
93
+ input: &ChargeSubscriptionInput,
94
+ payment_client: &impl PaymentGateway,
95
+ ) -> Result<Invoice, AppError> {
96
+ if input.customer_id.trim().is_empty() {
97
+ return Err(AppError::validation(
98
+ "El identificador de cliente es obligatorio.",
99
+ ));
100
+ }
101
+
102
+ if input.amount_cents == 0 {
103
+ return Err(AppError::validation(
104
+ "El monto de suscripción debe ser superior a cero.",
105
+ ));
106
+ }
107
+
108
+ let receipt = payment_client.process_charge(&input.customer_id, input.amount_cents)?;
109
+ Ok(Invoice::from_receipt(receipt))
110
+ }
111
+ ```
112
+
113
+ ---
114
+
115
+ ## 4. Sibling Skills & References
116
+
117
+ - **`best-practices`**: Universal clean code principles.
118
+ - **`ux-writing`**: Guidelines for friendly, persuasive user copy.
119
+ - Detailed config and error templates: `references/rust-error-and-clippy.md`.
@@ -0,0 +1,92 @@
1
+ # Rust Error Handling & Clippy Reference
2
+
3
+ ## 1. Domain Error Pattern with `thiserror`
4
+
5
+ ```rust
6
+ use thiserror::Error;
7
+
8
+ #[derive(Error, Debug)]
9
+ pub enum AppError {
10
+ #[error("Validación: {message}")]
11
+ Validation { message: String },
12
+
13
+ #[error("No encontrado: {resource} con id {id}")]
14
+ NotFound { resource: &'static str, id: String },
15
+
16
+ #[error("Error de pasarela de pago: {0}")]
17
+ PaymentGateway(String),
18
+
19
+ #[error("Error de base de datos")]
20
+ Database(#[from] sqlx::Error),
21
+ }
22
+
23
+ impl AppError {
24
+ pub fn validation(msg: impl Into<String>) -> Self {
25
+ Self::Validation { message: msg.into() }
26
+ }
27
+
28
+ pub fn not_found(resource: &'static str, id: impl Into<String>) -> Self {
29
+ Self::NotFound { resource, id: id.into() }
30
+ }
31
+
32
+ /// User-friendly message for UI responses
33
+ pub fn user_message(&self) -> String {
34
+ match self {
35
+ Self::Validation { message } => message.clone(),
36
+ Self::NotFound { resource, .. } => {
37
+ format!("No pudimos encontrar el recurso solicitado ({resource}).")
38
+ }
39
+ Self::PaymentGateway(_) => {
40
+ "Hubo un inconveniente al procesar tu pago. Por favor, verifica tus datos.".into()
41
+ }
42
+ Self::Database(_) => {
43
+ "Ocurrió un error inesperado al guardar la información. Intenta de nuevo en unos momentos.".into()
44
+ }
45
+ }
46
+ }
47
+ }
48
+ ```
49
+
50
+ ---
51
+
52
+ ## 2. Clippy & Linter Baseline (`Cargo.toml`)
53
+
54
+ Modern Rust codebases configure compiler warnings and Clippy directly in `Cargo.toml`:
55
+
56
+ ```toml
57
+ [lints.rust]
58
+ unsafe_code = "deny"
59
+ missing_debug_implementations = "warn"
60
+
61
+ [lints.clippy]
62
+ all = "warn"
63
+ pedantic = "warn"
64
+ unwrap_used = "deny"
65
+ expect_used = "warn"
66
+ panic = "deny"
67
+ manual_let_else = "warn"
68
+ needless_pass_by_value = "warn"
69
+ ```
70
+
71
+ ---
72
+
73
+ ## 3. Directory Layout (No `mod.rs` Barrels)
74
+
75
+ ```text
76
+ src/
77
+ ├── main.rs (or lib.rs)
78
+ ├── errors.rs # AppError definition
79
+ ├── actions/
80
+ │ ├── billing/
81
+ │ │ ├── charge_subscription.rs # One action per file
82
+ │ │ └── cancel_subscription.rs
83
+ │ └── billing.rs # Module entry (no mod.rs)
84
+ ├── domains/
85
+ │ ├── billing/
86
+ │ │ └── models.rs # Invoices, plans, pure structs
87
+ │ └── billing.rs
88
+ └── infrastructure/
89
+ ├── stripe/
90
+ │ └── client.rs
91
+ └── stripe.rs
92
+ ```
@@ -0,0 +1,109 @@
1
+ ---
2
+ name: testing-vitest
3
+ description: Clean, deterministic unit and integration testing with Vitest. Enforces Arrange-Act-Assert (AAA), behavioral testing over implementation details, in-memory fakes over brittle mocks, strict assertions, and boundary/edge case test matrices. TRIGGER when writing, maintaining, or fixing automated tests (*.test.ts, *.spec.ts), test utilities, or testing infrastructure with Vitest.
4
+ ---
5
+
6
+ # Vitest Testing Standards
7
+
8
+ Guidelines for authoring reliable, fast, and maintainable unit and integration tests with **Vitest**.
9
+
10
+ ---
11
+
12
+ ## 1. Golden Rules (Non-negotiable)
13
+
14
+ 1. **Test Behavior, Not Implementation Details**:
15
+ - Assert on observable inputs, outputs, side-effects, and state changes.
16
+ - Do not test private methods, internal variable states, or intermediate execution order.
17
+
18
+ 2. **Strict Arrange-Act-Assert (AAA) Structure**:
19
+ - Organize every test clearly into three visually distinct blocks:
20
+ - `// Arrange`: Set up inputs, state, and dependencies.
21
+ - `// Act`: Execute the unit under test.
22
+ - `// Assert`: Verify outputs and invariants.
23
+
24
+ 3. **In-Memory Fakes Over Brittle Mocks**:
25
+ - Prefer in-memory fakes (e.g., `FakeUserRepository`, `MemoryStorage`) over deeply nested `vi.spyOn()` or mock chains.
26
+ - Only mock external boundaries (network HTTP calls, system clocks, third-party APIs).
27
+
28
+ 4. **Deterministic & Isolated (Zero Shared Mutable State)**:
29
+ - Each test must run independently in any order without relying on state from preceding tests.
30
+ - Use `beforeEach` strictly to reset test fixtures.
31
+
32
+ 5. **Exhaustive Edge Cases & Failure Modes**:
33
+ - Always test boundary values: empty strings, zero, negative numbers, missing optional fields, expired tokens, and rejected promises.
34
+ - Never write happy-path-only tests.
35
+
36
+ 6. **Clear, Intention-Revealing Test Names**:
37
+ - Use descriptive sentences: `it("should calculate tax accurately when order total exceeds threshold")`.
38
+ - Avoid vague names like `it("works")` or `it("test 1")`.
39
+
40
+ ---
41
+
42
+ ## 2. Standard Test Pattern (AAA)
43
+
44
+ ```ts
45
+ import { describe, it, expect } from "vitest";
46
+ import { calculateDiscount } from "./calculate-discount";
47
+
48
+ describe("calculateDiscount", () => {
49
+ it("should apply 15% discount when user is a premium member", () => {
50
+ // Arrange
51
+ const originalPrice = 100;
52
+ const isPremium = true;
53
+
54
+ // Act
55
+ const finalPrice = calculateDiscount(originalPrice, isPremium);
56
+
57
+ // Assert
58
+ expect(finalPrice).toBe(85);
59
+ });
60
+
61
+ it("should return unchanged price when discount percentage is zero", () => {
62
+ // Arrange & Act
63
+ const finalPrice = calculateDiscount(50, false);
64
+
65
+ // Assert
66
+ expect(finalPrice).toBe(50);
67
+ });
68
+
69
+ it("should throw an AppError when input price is negative", () => {
70
+ // Arrange, Act & Assert
71
+ expect(() => calculateDiscount(-10, true)).toThrowError(/Price must be non-negative/);
72
+ });
73
+ });
74
+ ```
75
+
76
+ ---
77
+
78
+ ## 3. Mocking External Boundaries Safely
79
+
80
+ ```ts
81
+ import { describe, it, expect, vi, beforeEach } from "vitest";
82
+ import { fetchExchangeRate } from "./fetch-exchange-rate";
83
+
84
+ describe("fetchExchangeRate", () => {
85
+ beforeEach(() => {
86
+ vi.restoreAllMocks();
87
+ });
88
+
89
+ it("should parse rate safely when API returns 200", async () => {
90
+ const fakeFetch = vi.fn().mockResolvedValue({
91
+ ok: true,
92
+ json: async () => ({ rate: 1.08 }),
93
+ });
94
+ vi.stubGlobal("fetch", fakeFetch);
95
+
96
+ const rate = await fetchExchangeRate("EUR", "USD");
97
+
98
+ expect(rate).toBe(1.08);
99
+ expect(fakeFetch).toHaveBeenCalledWith(expect.stringContaining("EUR/USD"));
100
+ });
101
+ });
102
+ ```
103
+
104
+ ---
105
+
106
+ ## 4. Sibling Skills
107
+
108
+ - **`best-practices`**: Universal clean code rules.
109
+ - **`error-handling-logging`**: Testing expected `AppError` throws and error payloads.