@baselane/packs 0.1.6 → 0.1.8
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.
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "full-harness",
|
|
3
3
|
"version": "1.0.0",
|
|
4
|
-
"title": "Full harness
|
|
5
|
-
"summary": "The
|
|
4
|
+
"title": "Full harness",
|
|
5
|
+
"summary": "The maximal engineering harness in one pack: 278 skills, 67 agents, 94 commands, and the full rules corpus — every discipline, role, and command, unabridged.",
|
|
6
6
|
"scope": "both",
|
|
7
7
|
"attribution": {
|
|
8
8
|
"source": "affaan-m/ECC (Everything Claude Code)",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"license": "MIT"
|
|
11
11
|
},
|
|
12
12
|
"context": {
|
|
13
|
-
"markdown": "# Full harness — the complete Everything Claude Code (ECC) corpus\n\n<!-- adapted from affaan-m/ECC (Everything Claude Code) (MIT) — https://github.com/affaan-m/ECC -->\n\nThis pack mirrors the entire ECC library — 278 skills, 67 agents, 94 commands, and the full rules corpus below — in a single installable harness. Agent \"Prompt Defense Baseline\" boilerplate has been stripped; ECC's non-portable plugin-bootstrap hooks are intentionally omitted (they wrap a node -e loader that only runs inside ECC's own install). Everything else is the upstream content, unabridged.\n\n## Rules corpus\n\n\n### README\n\n# Rules\n\n## Structure\n\nRules are organized into a **common** layer plus **language-specific** directories:\n\n```\nrules/\n├── common/ # Language-agnostic principles (always install)\n│ ├── coding-style.md\n│ ├── git-workflow.md\n│ ├── testing.md\n│ ├── performance.md\n│ ├── patterns.md\n│ ├── hooks.md\n│ ├── agents.md\n│ └── security.md\n├── typescript/ # TypeScript/JavaScript specific\n├── angular/ # Angular specific\n├── vue/ # Vue 3 specific\n├── nuxt/ # Nuxt 4 specific\n├── python/ # Python specific\n├── golang/ # Go specific\n├── web/ # Web and frontend specific\n├── react-native/ # React Native / Expo specific\n├── swift/ # Swift specific\n├── php/ # PHP specific\n├── ruby/ # Ruby / Rails specific\n└── arkts/ # HarmonyOS / ArkTS specific\n```\n\n- **common/** contains universal principles — no language-specific code examples.\n- **Language directories** extend the common rules with framework-specific patterns, tools, and code examples. Each file references its common counterpart.\n\n## Installation\n\n### Option 1: Install Script (Recommended)\n\n```bash\n# Install common + one or more language-specific rule sets\n./install.sh typescript\n./install.sh angular\n./install.sh vue\n./install.sh nuxt\n./install.sh python\n./install.sh golang\n./install.sh web\n./install.sh react-native\n./install.sh swift\n./install.sh php\n./install.sh ruby\n./install.sh arkts\n\n# Install multiple languages at once\n./install.sh typescript python\n```\n\n### Option 2: Manual Installation\n\n> **Important:** Copy entire directories — do NOT flatten with `/*`.\n> Common and language-specific directories contain files with the same names.\n> Flattening them into one directory causes language-specific files to overwrite\n> common rules, and breaks the relative `../common/` references used by\n> language-specific files.\n>\n> Use the ECC-owned namespace below for user-level Claude installs. Flat\n> package-level destinations can collide with non-ECC rule packs and do not\n> match the main README guidance.\n\n```bash\n# Create the ECC rule namespace once.\nmkdir -p ~/.claude/rules/ecc\n\n# Install common rules (required for all projects)\ncp -r rules/common ~/.claude/rules/ecc/\n\n# Install language-specific rules based on your project's tech stack\ncp -r rules/typescript ~/.claude/rules/ecc/\ncp -r rules/angular ~/.claude/rules/ecc/\ncp -r rules/vue ~/.claude/rules/ecc/\ncp -r rules/nuxt ~/.claude/rules/ecc/\ncp -r rules/python ~/.claude/rules/ecc/\ncp -r rules/golang ~/.claude/rules/ecc/\ncp -r rules/web ~/.claude/rules/ecc/\ncp -r rules/react-native ~/.claude/rules/ecc/\ncp -r rules/swift ~/.claude/rules/ecc/\ncp -r rules/php ~/.claude/rules/ecc/\ncp -r rules/ruby ~/.claude/rules/ecc/\ncp -r rules/arkts ~/.claude/rules/ecc/\n\n# Attention ! ! ! Configure according to your actual project requirements; the configuration here is for reference only.\n```\n\nFor project-local rules, use the same namespace under the project root:\n\n```bash\nmkdir -p .claude/rules/ecc\ncp -r rules/common .claude/rules/ecc/\ncp -r rules/typescript .claude/rules/ecc/\n```\n\n## Rules vs Skills\n\n- **Rules** define standards, conventions, and checklists that apply broadly (e.g., \"80% test coverage\", \"no hardcoded secrets\").\n- **Skills** (`skills/` directory) provide deep, actionable reference material for specific tasks (e.g., `python-patterns`, `golang-testing`).\n\nLanguage-specific rule files reference relevant skills where appropriate. Rules tell you _what_ to do; skills tell you _how_ to do it.\n\n## Adding a New Language\n\nTo add support for a new language (e.g., `rust/`):\n\n1. Create a `rules/rust/` directory\n2. Add files that extend the common rules:\n - `coding-style.md` — formatting tools, idioms, error handling patterns\n - `testing.md` — test framework, coverage tools, test organization\n - `patterns.md` — language-specific design patterns\n - `hooks.md` — PostToolUse hooks for formatters, linters, type checkers\n - `security.md` — secret management, security scanning tools\n3. Each file should start with:\n ```\n > This file extends [common/xxx.md](../common/xxx.md) with <Language> specific content.\n ```\n4. Reference existing skills if available, or create new ones under `skills/`.\n\nFor non-language domains like `web/`, follow the same layered pattern when there is enough reusable domain-specific guidance to justify a standalone ruleset.\n\n## Rule Priority\n\nWhen language-specific rules and common rules conflict, **language-specific rules take precedence** (specific overrides general). This follows the standard layered configuration pattern (similar to CSS specificity or `.gitignore` precedence).\n\n- `rules/common/` defines universal defaults applicable to all projects.\n- `rules/golang/`, `rules/python/`, `rules/swift/`, `rules/php/`, `rules/typescript/`, `rules/react-native/`, etc. override those defaults where language idioms differ.\n\n### Example\n\n`common/coding-style.md` recommends immutability as a default principle. A language-specific `golang/coding-style.md` can override this:\n\n> Idiomatic Go uses pointer receivers for struct mutation — see [common/coding-style.md](../common/coding-style.md) for the general principle, but Go-idiomatic mutation is preferred here.\n\n### Common rules with override notes\n\nRules in `rules/common/` that may be overridden by language-specific files are marked with:\n\n> **Language note**: This rule may be overridden by language-specific rules for languages where this pattern is not idiomatic.\n\n### angular/coding-style\n\n# Angular Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Angular specific content.\n\n## Version Awareness\n\nAlways check the project's Angular version before writing code — features differ significantly between versions. Run `ng version` or inspect `package.json`. When creating a new project, do not pin a version unless the user specifies one.\n\nAfter generating or modifying Angular code, always run `ng build` to catch errors before finishing.\n\n## File Naming\n\nFollow Angular CLI conventions — one artifact per file:\n\n- `user-profile.component.ts` + `user-profile.component.html` + `user-profile.component.spec.ts`\n- `user.service.ts`, `auth.guard.ts`, `date-format.pipe.ts`\n- Feature folders: `features/users/`, `features/auth/`\n- Generate with the CLI: `ng generate component features/users/user-card`\n\n## Components\n\nPrefer standalone components (v17+ default). Use `OnPush` change detection on all new components.\n\n```typescript\n@Component({\n selector: 'app-user-card',\n standalone: true,\n imports: [RouterModule],\n templateUrl: './user-card.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class UserCardComponent {\n user = input.required<User>();\n select = output<string>();\n}\n```\n\n## Dependency Injection\n\nUse `inject()` over constructor injection. Keep constructors empty or remove them entirely.\n\n```typescript\n// CORRECT\n@Injectable({ providedIn: 'root' })\nexport class UserService {\n private http = inject(HttpClient);\n private router = inject(Router);\n}\n\n// WRONG: Constructor injection is verbose and harder to tree-shake\nconstructor(private http: HttpClient, private router: Router) {}\n```\n\nUse `InjectionToken` for non-class dependencies:\n\n```typescript\nconst API_URL = new InjectionToken<string>('API_URL');\n\n// Provide:\n{ provide: API_URL, useValue: 'https://api.example.com' }\n\n// Consume:\nprivate apiUrl = inject(API_URL);\n```\n\n## Signals\n\n### Core Primitives\n\n```typescript\ncount = signal(0);\ndoubled = computed(() => this.count() * 2);\n\nincrement() {\n this.count.update(n => n + 1);\n}\n```\n\n### `linkedSignal` — Writable Derived State\n\nUse `linkedSignal` when a signal must reset or adapt when a source changes, but also be independently writable:\n\n```typescript\nselectedOption = linkedSignal(() => this.options()[0]);\n// Resets to first option when options changes, but user can override\n```\n\n### `resource` — Async Data into Signals\n\nUse `resource()` to fetch async data reactively without manual subscriptions:\n\n```typescript\nuserResource = resource({\n request: () => ({ id: this.userId() }),\n loader: ({ request }) => fetch(`/api/users/${request.id}`).then(r => r.json()),\n});\n\n// Access: userResource.value(), userResource.isLoading(), userResource.error()\n```\n\n### `effect` Usage\n\nUse `effect()` only for side effects that must react to signal changes (logging, third-party DOM manipulation). Never use effects to synchronize signals — use `computed` or `linkedSignal` instead. For DOM work after render, use `afterRenderEffect`.\n\n```typescript\n// CORRECT: Side effect\neffect(() => console.log('User changed:', this.user()));\n\n// WRONG: Use computed instead\neffect(() => { this.fullName.set(`${this.first()} ${this.last()}`); });\n```\n\n## Templates\n\nUse v17+ block syntax. Always provide `track` in `@for`:\n\n```html\n@for (item of items(); track item.id) {\n <app-item [item]=\"item\" />\n}\n\n@if (isLoading()) {\n <app-spinner />\n} @else if (error()) {\n <app-error [message]=\"error()\" />\n} @else {\n <app-content [data]=\"data()\" />\n}\n```\n\nNo logic in templates beyond simple conditionals — move to component methods or pipes.\n\n## Forms\n\nChoose the form strategy that matches the project's existing approach:\n\n- **Signal Forms** (v21+): Preferred for new projects on v21+. Signal-based form state.\n- **Reactive Forms**: `FormBuilder` + `FormGroup` + `FormControl`. Best for complex forms with dynamic validation.\n- **Template-Driven Forms**: `ngModel`. Suitable for simple forms only.\n\n```typescript\n// Reactive Forms — standard approach for most apps\nexport class LoginComponent {\n private fb = inject(FormBuilder);\n\n form = this.fb.group({\n email: ['', [Validators.required, Validators.email]],\n password: ['', [Validators.required, Validators.minLength(8)]],\n });\n\n submit() {\n if (this.form.valid) {\n // use this.form.value\n }\n }\n}\n```\n\n## Component Styles\n\nUse component-level styles with `ViewEncapsulation.Emulated` (default). Avoid `ViewEncapsulation.None` unless building a design system that intentionally bleeds styles.\n\n- Scope styles to the component — do not use global class names inside component stylesheets\n- Use `:host` for host element styling\n- Prefer CSS custom properties for themeable values\n\n## Change Detection\n\n- Default to `ChangeDetectionStrategy.OnPush` on all new components\n- Signals and `async` pipe handle detection automatically — avoid `markForCheck()` and `detectChanges()`\n- Never mutate `@Input()` objects in place when using OnPush\n\n### angular/hooks\n\n# Angular Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Angular specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **Prettier**: Auto-format `.ts` and `.html` files after edit\n- **ESLint / ng lint**: Run `ng lint` after editing Angular source files to catch decorator misuse, template errors, and style violations\n- **TypeScript check**: Run `tsc --noEmit` after editing `.ts` files\n- **Build check**: Run `ng build` after generating or significantly changing Angular code to catch template and type errors early\n\n## Stop Hooks\n\n- **Lint audit**: Run `ng lint` across modified files before session ends to catch any outstanding violations\n\n### angular/patterns\n\n# Angular Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Angular specific content.\n\n## Smart / Dumb Component Split\n\nSmart (container) components own data fetching and state. Dumb (presentational) components receive inputs and emit outputs only — no service injection.\n\n```typescript\n// Smart — owns data\n@Component({ standalone: true, changeDetection: ChangeDetectionStrategy.OnPush })\nexport class UserPageComponent {\n private userService = inject(UserService);\n user = toSignal(this.userService.getUser(this.userId));\n}\n```\n\n```html\n<!-- Dumb — pure presentation -->\n<app-user-card [user]=\"user()\" (select)=\"onSelect($event)\" />\n```\n\n## Service Layer\n\nServices own all data access and business logic. Components delegate — no `HttpClient` in components.\n\n```typescript\n@Injectable({ providedIn: 'root' })\nexport class UserService {\n private http = inject(HttpClient);\n\n getUsers(): Observable<User[]> {\n return this.http.get<User[]>('/api/users');\n }\n}\n```\n\n## Async Data with `resource`\n\nUse `resource()` for reactive async fetching. Prefer over manual RxJS pipelines for simple data loading:\n\n```typescript\nexport class UserDetailComponent {\n userId = input.required<string>();\n\n userResource = resource({\n request: () => ({ id: this.userId() }),\n loader: ({ request }) =>\n firstValueFrom(inject(UserService).getUser(request.id)),\n });\n}\n```\n\nAccess state: `userResource.value()`, `userResource.isLoading()`, `userResource.error()`, `userResource.reload()`.\n\n## Signal State Patterns\n\n```typescript\n// Local mutable state\ncount = signal(0);\n\n// Derived (never duplicated)\ndoubled = computed(() => this.count() * 2);\n\n// Writable derived state that resets with source\nselectedItem = linkedSignal(() => this.items()[0]);\n\n// Bridge Observable to signal\nusers = toSignal(this.userService.getUsers(), { initialValue: [] });\n```\n\nNever store derived values in separate signals — use `computed`. Never use `effect` to sync signals — use `computed` or `linkedSignal`.\n\n## Subscription Cleanup\n\nUse `takeUntilDestroyed()` for all manual subscriptions. Never use manual `ngOnDestroy` + `Subject` + `takeUntil` on new code.\n\n```typescript\nexport class UserComponent {\n private destroyRef = inject(DestroyRef);\n\n ngOnInit() {\n this.userService.updates$\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(update => this.handleUpdate(update));\n }\n}\n```\n\n## Routing\n\n### Route Definition\n\n```typescript\n// app.routes.ts\nexport const routes: Routes = [\n { path: '', component: HomeComponent },\n {\n path: 'admin',\n canMatch: [authGuard], // CanMatch prevents loading the chunk at all\n loadChildren: () => import('./admin/admin.routes').then(m => m.ADMIN_ROUTES),\n },\n {\n path: 'users/:id',\n resolve: { user: userResolver },\n component: UserDetailComponent,\n },\n];\n```\n\n- Use `canMatch` over `canActivate` when the route module should not load for unauthorized users\n- Lazy-load all feature modules with `loadChildren`\n- Pre-fetch data with `resolve` to avoid loading states in components\n\n### Functional Guards\n\n```typescript\nexport const authGuard: CanActivateFn = () => {\n const auth = inject(AuthService);\n return auth.isAuthenticated()\n ? true\n : inject(Router).createUrlTree(['/login']);\n};\n```\n\n### Data Resolvers\n\n```typescript\nexport const userResolver: ResolveFn<User> = (route) => {\n return inject(UserService).getUser(route.paramMap.get('id')!);\n};\n```\n\n### View Transitions\n\nEnable smooth route transitions with the View Transitions API:\n\n```typescript\n// app.config.ts\nprovideRouter(routes, withViewTransitions())\n```\n\n## Dependency Injection Patterns\n\n### Scoped Providers\n\nProvide services at component or route level when they should not be singletons:\n\n```typescript\n@Component({\n providers: [UserEditService], // scoped to this component subtree\n})\nexport class UserEditComponent {}\n```\n\n### `InjectionToken`\n\n```typescript\nexport const CONFIG = new InjectionToken<AppConfig>('APP_CONFIG');\n\n// In providers:\n{ provide: CONFIG, useValue: appConfig }\n{ provide: CONFIG, useFactory: () => loadConfig(), deps: [] }\n\n// Consume:\nprivate config = inject(CONFIG);\n```\n\n### `viewProviders` vs `providers`\n\n- `providers`: Available to the component and all its content children\n- `viewProviders`: Available only to the component's own view (not projected content)\n\n## HTTP Interceptors\n\nUse functional interceptors (v15+) for auth, error handling, and retries:\n\n```typescript\nexport const authInterceptor: HttpInterceptorFn = (req, next) => {\n const token = inject(AuthService).token();\n if (!token) return next(req);\n return next(req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }));\n};\n```\n\nRegister in `app.config.ts`:\n\n```typescript\nprovideHttpClient(withInterceptors([authInterceptor, errorInterceptor]))\n```\n\n## RxJS Operators\n\n- `switchMap` — search, navigation (cancels previous)\n- `mergeMap` — independent parallel requests\n- `exhaustMap` — form submissions (ignores until complete)\n- Always handle errors with `catchError` — never let streams die silently\n\n```typescript\nsearch$ = this.query$.pipe(\n debounceTime(300),\n distinctUntilChanged(),\n switchMap(q => this.service.search(q).pipe(catchError(() => of([])))),\n);\n```\n\n## Forms\n\nMatch the project's existing form strategy. For new v21+ apps, prefer signal forms.\n\n```typescript\n// Reactive Forms — standard for complex forms\nexport class UserFormComponent {\n private fb = inject(FormBuilder);\n\n form = this.fb.group({\n name: ['', Validators.required],\n email: ['', [Validators.required, Validators.email]],\n });\n}\n```\n\n## Rendering Strategies\n\n- **CSR** (default): Standard SPA\n- **SSR + Hydration**: `ng add @angular/ssr` — improves FCP and SEO\n- **SSG (Prerendering)**: Static pages at build time for content-heavy routes\n\nWhen using SSR, avoid `window`, `document`, `localStorage` directly — use `isPlatformBrowser` or `DOCUMENT` token.\n\n## Accessibility\n\nUse Angular CDK for headless, accessible components (Accordion, Listbox, Combobox, Menu, Tabs, Toolbar, Tree, Grid). Style ARIA attributes rather than managing them manually:\n\n```css\n[aria-selected=\"true\"] { background: var(--color-selected); }\n```\n\n## Skill Reference\n\nSee skill: `angular-developer` for deep guidance on signals, forms, routing, DI, SSR, and accessibility patterns.\n\n### angular/security\n\n# Angular Security\n\n> This file extends [common/security.md](../common/security.md) with Angular specific content.\n\n## XSS Prevention\n\nAngular auto-sanitizes bound values. Never bypass the sanitizer on user-controlled input.\n\n```typescript\n// WRONG: Bypasses sanitization — XSS risk\nthis.safeHtml = this.sanitizer.bypassSecurityTrustHtml(userInput);\n\n// CORRECT: Sanitize explicitly before trusting\nthis.safeHtml = this.sanitizer.sanitize(SecurityContext.HTML, userInput);\n```\n\n- Never use `bypassSecurityTrust*` methods without a documented, reviewed reason\n- Avoid `[innerHTML]` with untrusted content — use `innerText` or a sanitizing pipe\n- Never bind `[href]` to user input — Angular does not block `javascript:` URLs in all contexts\n- Never construct template strings from user data\n\n## HTTP Security\n\nUse `HttpClient` exclusively — never raw `fetch()` or `XHR` unless no alternative exists.\n\n```typescript\n// WRONG: Bypasses interceptors (auth headers, error handling, logging)\nconst res = await fetch('/api/users');\n\n// CORRECT\nusers$ = this.http.get<User[]>('/api/users');\n```\n\n- Attach auth tokens via interceptors — never hardcode in individual service calls\n- Type and validate API responses — treat external data as `unknown` at the boundary\n- Never log HTTP responses that may contain tokens, PII, or credentials\n\n## Secret Management\n\n```typescript\n// WRONG: Hardcoded secret in source\nconst apiKey = 'sk-live-xxxx';\n\n// CORRECT: Injected via environment\nimport { environment } from '../environments/environment';\nconst apiKey = environment.apiKey;\n```\n\n- Treat `environment.ts` as a config shape — never store real secrets in source-controlled environment files\n- Inject production secrets via CI/CD (environment variables, secret managers)\n\n## Route Guards\n\nEvery authenticated or role-restricted route must have a guard. Never rely on hiding UI elements alone.\n\n```typescript\n{\n path: 'admin',\n canMatch: [authGuard, roleGuard('admin')],\n loadChildren: () => import('./admin/admin.routes'),\n}\n```\n\nUse `canMatch` for sensitive routes — it prevents the route module from loading at all for unauthorized users.\n\n## SSR Security\n\nWhen using Angular SSR:\n\n- Never expose server-side environment variables to the client via `TransferState` unless they are intentionally public\n- Sanitize all inputs before server-side rendering — DOM-based XSS can occur server-side too\n- Avoid `window`, `document`, `localStorage` on the server — gate with `isPlatformBrowser` or inject via `DOCUMENT` token\n\n## Content Security Policy\n\nConfigure CSP headers server-side. Avoid `unsafe-inline` in `script-src`. When using SSR with inline scripts, use nonces via Angular's CSP support.\n\n## Agent Support\n\n- Use **security-reviewer** skill for comprehensive security audits\n\n### angular/testing\n\n# Angular Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Angular specific content.\n\n## Test Runner\n\nUse the test runner configured by the project. Check `angular.json` and `package.json`; Angular projects commonly use Vitest, Jest, or Jasmine + Karma.\n\n```bash\nng test # watch mode\nng test --no-watch # CI mode\n```\n\n## TestBed Setup\n\nFor standalone components, import the component directly. Call `compileComponents()` for components with external templates.\n\n```typescript\ndescribe('UserCardComponent', () => {\n let fixture: ComponentFixture<UserCardComponent>;\n\n beforeEach(async () => {\n await TestBed.configureTestingModule({\n imports: [UserCardComponent],\n }).compileComponents();\n\n fixture = TestBed.createComponent(UserCardComponent);\n });\n});\n```\n\n## Signal Inputs\n\nSet signal-based inputs via `fixture.componentRef.setInput()`:\n\n```typescript\nfixture.componentRef.setInput('user', mockUser);\nfixture.detectChanges();\n```\n\n## Component Harnesses\n\nPrefer Angular CDK component harnesses over direct DOM queries for UI interaction. Harnesses are more resilient to markup changes.\n\n```typescript\nimport { HarnessLoader } from '@angular/cdk/testing';\nimport { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';\nimport { MatButtonHarness } from '@angular/material/button/testing';\n\nlet loader: HarnessLoader;\n\nbeforeEach(() => {\n loader = TestbedHarnessEnvironment.loader(fixture);\n});\n\nit('triggers save on button click', async () => {\n const button = await loader.getHarness(MatButtonHarness.with({ text: 'Save' }));\n await button.click();\n expect(saveSpy).toHaveBeenCalled();\n});\n```\n\n## Router Testing\n\nUse `RouterTestingHarness` for components that depend on the router:\n\n```typescript\nimport { RouterTestingHarness } from '@angular/router/testing';\n\nit('renders user on navigation', async () => {\n const harness = await RouterTestingHarness.create();\n const component = await harness.navigateByUrl('/users/1', UserDetailComponent);\n expect(component.userId()).toBe('1');\n});\n```\n\n## Async Testing\n\nUse `fakeAsync` + `tick` for controlled async. Use `waitForAsync` for real async with `fixture.whenStable()`.\n\n```typescript\nit('loads user after delay', fakeAsync(() => {\n const service = TestBed.inject(UserService);\n vi.spyOn(service, 'getUser').mockReturnValue(of(mockUser));\n\n fixture.detectChanges();\n tick();\n fixture.detectChanges();\n\n expect(fixture.nativeElement.querySelector('.name').textContent).toBe(mockUser.name);\n}));\n```\n\n## HTTP Testing\n\n```typescript\nimport { provideHttpClientTesting } from '@angular/common/http/testing';\nimport { HttpTestingController } from '@angular/common/http/testing';\n\nbeforeEach(() => {\n TestBed.configureTestingModule({\n providers: [provideHttpClient(), provideHttpClientTesting()],\n });\n httpMock = TestBed.inject(HttpTestingController);\n});\n\nafterEach(() => httpMock.verify());\n```\n\n## Service Testing\n\nInject services directly without a component fixture:\n\n```typescript\ndescribe('UserService', () => {\n let service: UserService;\n\n beforeEach(() => {\n TestBed.configureTestingModule({\n providers: [provideHttpClient(), provideHttpClientTesting()],\n });\n service = TestBed.inject(UserService);\n });\n});\n```\n\n## What to Test\n\n- **Services**: All public methods, error paths, HTTP interactions\n- **Components**: Input/output bindings, rendered output for key states, user interactions via harnesses\n- **Pipes**: Pure transformation — plain unit tests, no TestBed needed\n- **Guards/Resolvers**: Return values for allowed and denied states using `RouterTestingHarness`\n\n## E2E Testing\n\nUse the project's configured E2E framework, such as Cypress or Playwright, for critical user flows.\n\n```typescript\ndescribe('Login flow', () => {\n it('redirects to dashboard on valid credentials', () => {\n cy.visit('/login');\n cy.get('[data-cy=email]').type('user@example.com');\n cy.get('[data-cy=password]').type('password123');\n cy.get('[data-cy=submit]').click();\n cy.url().should('include', '/dashboard');\n });\n});\n```\n\n- Add `data-cy` attributes to interactive elements for stable selectors\n- Do not rely on CSS classes or text content for selectors in E2E tests\n\n## Coverage\n\nTarget ≥80% for services and pipes. Components: test behaviour, not implementation details.\n\n## Skill Reference\n\nSee skill: `angular-developer` for comprehensive testing patterns, harness usage, and async best practices.\n\n### arkts/coding-style\n\n# HarmonyOS / ArkTS Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with HarmonyOS and ArkTS-specific content.\n\n## ArkTS Language Constraints\n\nArkTS is a strict, statically-typed subset of TypeScript. Violating these constraints causes **compilation failures**.\n\n### Type System\n\n- No `any` or `unknown` types - always use explicit types\n- No index access types - use type names directly\n- No conditional type aliases or `infer` keyword\n- No intersection types - use inheritance\n- No mapped types - use classes and regular idioms\n- No `typeof` for type annotations - use explicit type declarations\n- No `as const` assertions - use explicit type annotations\n- No structural typing - use inheritance, interfaces, or type aliases\n- No TypeScript utility types except `Partial`, `Required`, `Readonly`, `Record`\n- For `Record<K, V>`, index expression type is `V | undefined`\n- Omit type annotations in `catch` clauses (ArkTS does not support `any`/`unknown`)\n\n### Functions & Classes\n\n- No function expressions - use arrow functions\n- No nested functions - use lambdas\n- No generator functions - use `async`/`await` for multitasking\n- No `Function.apply`, `Function.call`, `Function.bind` - follow traditional OOP for `this`\n- No constructor type expressions - use lambdas\n- No constructor signatures in interfaces or object types - use methods or classes\n- No declaring class fields in constructors - declare in class body\n- No `this` in standalone functions or static methods - only in instance methods\n- No `new.target`\n- No definite assignment assertions (`let v!: T`) - use initialized declarations\n- No class literals - introduce named class types\n- No using classes as objects (assigning to variables) - class declarations introduce types, not values\n- Only one static block per class - merge all static statements\n\n### Object & Property Access\n\n- No dynamic field declaration or `obj[\"field\"]` access - use `obj.field` syntax\n- No `delete` operator - use nullable type with `null` to mark absence\n- No prototype assignment - use classes and interfaces\n- No `in` operator - use `instanceof`\n- No reassigning object methods - use wrapper functions or inheritance\n- No `Symbol()` API (except `Symbol.iterator`)\n- No `globalThis` or global scope - use explicit module exports/imports\n- No namespaces as objects - use classes or modules\n- No statements inside namespaces - use functions\n\n### Destructuring & Spread\n\n- No destructuring assignments or variable declarations - use intermediate objects and field-by-field access\n- No destructuring parameter declarations - pass parameters directly, assign local names manually\n- Spread operator only for expanding arrays (or array-derived classes) into rest parameters or array literals\n\n### Modules & Imports\n\n- No `require()` - use regular `import` syntax\n- No `export = ...` - use normal export/import\n- No import assertions - imports are compile-time in ArkTS\n- No UMD modules\n- No wildcards in module names\n- All `import` statements must appear before all other statements\n- TypeScript codebases must not depend on ArkTS codebases via import (reverse is supported)\n\n### Other Restrictions\n\n- No `var` - use `let`\n- No `for...in` loops - use regular `for` loops for arrays\n- No `with` statements\n- No JSX expressions\n- No `#` private identifiers - use `private` keyword\n- No declaration merging (classes, interfaces, enums) - keep definitions compact\n- No index signatures - use arrays\n- Comma operator only in `for` loops\n- Unary operators `+`, `-`, `~` only for numeric types (no implicit string conversion)\n- Enum members: only same-type compile-time expressions for explicit initializers\n- Function return type inference is limited - specify return types explicitly when calling functions with omitted return types\n\n### Object Literals\n\n- Supported only when compiler can infer the corresponding class or interface\n- NOT supported for: `any`/`Object`/`object` types, classes/interfaces with methods, classes with parameterized constructors, classes with `readonly` fields\n\n## Naming Conventions\n\n- Variables / functions: `camelCase` (e.g., `getUserInfo`, `goodsList`)\n- Classes / interfaces: `PascalCase` (e.g., `UserViewModel`, `IGoodsModel`)\n- Constants: `UPPER_SNAKE_CASE` (e.g., `MAX_PAGE_SIZE`, `COLOR_PRIMARY`)\n- File names: `PascalCase` for components (e.g., `HomePage.ets`), `camelCase` for utilities\n\n## Formatting\n\n- Prefer double quotes for strings\n- Semicolons at end of statements\n- Never use `var` - prefer `const`, then `let`\n- All methods, parameters, return values must have complete type annotations\n\n## File Organization\n\n- Component files (`.ets`): one `@ComponentV2` per file\n- ViewModel files: one ViewModel class per file\n- Model files: related data models may share a file\n- Keep files under 400 lines; extract helpers for files approaching 800 lines\n\n## Comments\n\n- File header: `@file` (file purpose) + `@author` (developer), if the project already uses file headers\n- Public methods: JSDoc with `@param`, `@returns`; add `@example` for complex methods\n- Match the project's existing documentation language; use English unless the repository has already standardized on Chinese comments\n\n## Error Handling\n\n```typescript\n// Use try/catch with proper error handling\ntry {\n const result = await riskyOperation()\n return result\n} catch (error) {\n hilog.error(0x0000, 'TAG', 'Operation failed: %{public}s', error)\n throw new Error('User-friendly error message')\n}\n```\n\n## Immutability\n\nFollow the common immutability principles - create new objects instead of mutating:\n\n```typescript\n// BAD: mutation\nfunction updateUser(user: UserModel, name: string): UserModel {\n user.name = name // direct mutation\n return user\n}\n\n// GOOD: immutable - create new instance\nfunction updateUser(user: UserModel, name: string): UserModel {\n const updated = new UserModel()\n updated.id = user.id\n updated.name = name\n updated.email = user.email\n return updated\n}\n```\n\n### arkts/hooks\n\n# HarmonyOS / ArkTS Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with HarmonyOS-specific build and validation hooks.\n\n## Build Commands\n\n### HAP Package Build\n\n```bash\n# Build HAP package (global hvigor environment)\nhvigorw assembleHap -p product=default\n\n# Build with specific module\nhvigorw assembleHap -p module=entry -p product=default\n\n# Clean build\nhvigorw clean\n```\n\n### DevEco Studio CLI\n\n```bash\n# Check project structure\nhvigorw --version\n\n# Install dependencies\nohpm install\n\n# Update dependencies\nohpm update\n```\n\n## Recommended PostToolUse Hooks\n\n### After Editing .ets/.ts Files\n\nRun hvigor build to check for ArkTS compilation errors:\n\n```json\n{\n \"type\": \"PostToolUse\",\n \"matcher\": {\n \"tool\": [\"Edit\", \"Write\"],\n \"filePath\": [\"**/*.ets\", \"**/*.ts\"]\n },\n \"hooks\": [\n {\n \"command\": \"hvigorw assembleHap -p product=default 2>&1 | tail -20\",\n \"async\": true,\n \"timeout\": 60000\n }\n ]\n}\n```\n\n### After Editing module.json5\n\nValidate permission and ability declarations:\n\n```json\n{\n \"type\": \"PostToolUse\",\n \"matcher\": {\n \"tool\": \"Edit\",\n \"filePath\": \"**/module.json5\"\n },\n \"hooks\": [\n {\n \"command\": \"echo '[HarmonyOS] module.json5 modified - verify permissions and abilities'\",\n \"async\": false\n }\n ]\n}\n```\n\n### After Editing oh-package.json5\n\nReinstall dependencies:\n\n```json\n{\n \"type\": \"PostToolUse\",\n \"matcher\": {\n \"tool\": \"Edit\",\n \"filePath\": \"**/oh-package.json5\"\n },\n \"hooks\": [\n {\n \"command\": \"ohpm install 2>&1 | tail -10\",\n \"async\": true,\n \"timeout\": 30000\n }\n ]\n}\n```\n\n## PreToolUse Hooks\n\n### V1 Decorator Guard\n\nWarn when code contains V1 state management decorators:\n\n```json\n{\n \"type\": \"PreToolUse\",\n \"matcher\": {\n \"tool\": [\"Write\", \"Edit\"],\n \"filePath\": \"**/*.ets\"\n },\n \"hooks\": [\n {\n \"command\": \"echo '[HarmonyOS] Reminder: Use @ComponentV2 / @Local / @Param - V1 decorators (@State, @Prop, @Link) are prohibited'\"\n }\n ]\n}\n```\n\n## Validation Checklist\n\nAfter each implementation cycle, verify:\n\n- [ ] `hvigorw assembleHap` completes without errors\n- [ ] No V1 decorators in new or modified `.ets` files\n- [ ] No `@ohos.router` imports in new or modified files\n- [ ] All API permissions declared in `module.json5`\n- [ ] All dependencies listed in `oh-package.json5`\n- [ ] Resource strings added to all i18n directories\n- [ ] Dark theme colors provided for new color resources\n\n### arkts/patterns\n\n# HarmonyOS / ArkTS Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with HarmonyOS and ArkTS-specific patterns.\n\n## State Management: V2 Only\n\n**MUST use** ArkUI State Management V2. V1 decorators are deprecated and must not be used.\n\n### V2 Decorators\n\n| Decorator | Purpose |\n|-----------|---------|\n| `@ComponentV2` | Marks a struct as a V2 component |\n| `@Local` | Local state within a component |\n| `@Param` | Props received from parent (read-only) |\n| `@Event` | Callback events from child to parent |\n| `@Provider` | Provides state to descendant components |\n| `@Consumer` | Consumes state from ancestor `@Provider` |\n| `@Monitor` | Watches for state changes (replaces V1 `@Watch`) |\n| `@Computed` | Derived/computed values |\n| `@ObservedV2` | Makes a class observable for V2 state management |\n| `@Trace` | Marks observable properties in `@ObservedV2` classes |\n\n### Prohibited V1 Decorators\n\nNever use: `@State`, `@Prop`, `@Link`, `@ObjectLink`, `@Observed`, `@Provide`, `@Consume`, `@Watch`, `@Component` (use `@ComponentV2` instead).\n\n### V2 Component Example\n\n```typescript\n@ObservedV2\nclass UserModel {\n @Trace name: string = ''\n @Trace age: number = 0\n}\n\n@ComponentV2\nstruct UserCard {\n @Param user: UserModel = new UserModel()\n @Event onDelete: () => void = () => {}\n\n build() {\n Column() {\n Text(this.user.name)\n .fontSize($r('app.float.font_size_title'))\n Text(`${this.user.age}`)\n .fontSize($r('app.float.font_size_body'))\n Button($r('app.string.delete'))\n .onClick(() => this.onDelete())\n }\n }\n}\n```\n\n### State Synchronization\n\n```typescript\n@ComponentV2\nstruct ParentPage {\n @Provider('userState') userModel: UserModel = new UserModel()\n\n build() {\n Column() {\n ChildComponent() // automatically receives @Consumer('userState')\n }\n }\n}\n\n@ComponentV2\nstruct ChildComponent {\n @Consumer('userState') userModel: UserModel = new UserModel()\n\n build() {\n Text(this.userModel.name)\n }\n}\n```\n\n## Routing: Navigation Only\n\n**MUST use** `Navigation` component with `NavPathStack`. Never use `@ohos.router`.\n\n### Navigation Setup\n\n```typescript\n@ComponentV2\nstruct MainPage {\n @Local navPathStack: NavPathStack = new NavPathStack()\n\n build() {\n Navigation(this.navPathStack) {\n // Home content\n }\n .navDestination(this.routerMap)\n }\n\n @Builder\n routerMap(name: string, param: ESObject) {\n if (name === 'detail') {\n DetailPage()\n } else if (name === 'settings') {\n SettingsPage()\n }\n }\n}\n```\n\n### Page Navigation\n\n```typescript\n// Push a new page\nthis.navPathStack.pushPath({ name: 'detail', param: { id: '123' } })\n\n// Replace current page\nthis.navPathStack.replacePath({ name: 'settings' })\n\n// Pop back\nthis.navPathStack.pop()\n\n// Pop to root\nthis.navPathStack.clear()\n```\n\n### NavDestination Sub-page\n\n```typescript\n@ComponentV2\nstruct DetailPage {\n build() {\n NavDestination() {\n Column() {\n Text($r('app.string.detail_title'))\n }\n }\n .title($r('app.string.detail_nav_title'))\n }\n}\n```\n\n## Architecture Pattern: MVVM\n\nRecommended architecture for HarmonyOS applications:\n\n```\nfeature/\n |-- model/ # Data models (@ObservedV2 classes)\n |-- viewmodel/ # Business logic (ViewModel classes)\n |-- view/ # UI components (@ComponentV2 structs)\n |-- service/ # API calls, data access\n```\n\n- **View**: Only rendering logic, no business logic in `build()`\n- **ViewModel**: All business logic encapsulated here\n- **Model**: Pure data classes with `@ObservedV2` and `@Trace`\n- **Service**: Network requests, database operations, file I/O\n\n## ArkUI Animation Patterns\n\n### State-Driven Animation\n\n```typescript\n@ComponentV2\nstruct AnimatedCard {\n @Local isExpanded: boolean = false\n @Local cardScale: number = 0.8\n\n build() {\n Column() {\n // Content\n }\n .scale({ x: this.cardScale, y: this.cardScale })\n .animation({ duration: 300, curve: Curve.EaseInOut })\n .onClick(() => {\n this.isExpanded = !this.isExpanded\n this.cardScale = this.isExpanded ? 1.0 : 0.8\n })\n }\n}\n```\n\n### Animation Rules\n\n- Prefer native HarmonyOS animation APIs and advanced templates\n- Use declarative UI with state-driven animations (change state variables to trigger animations)\n- Set `renderGroup(true)` for complex sub-component animations to reduce render batches\n- **NEVER** frequently change `width`, `height`, `padding`, `margin` during animations - severe performance impact\n- Use `animateTo` for explicit animation control\n- Prefer `transform` (translate, scale, rotate) and `opacity` for performant animations\n\n## Performance Patterns\n\n### LazyForEach for Large Lists\n\n```typescript\n@ComponentV2\nstruct LargeList {\n @Local dataSource: MyDataSource = new MyDataSource()\n\n build() {\n List() {\n LazyForEach(this.dataSource, (item: ItemModel) => {\n ListItem() {\n ItemComponent({ item: item })\n }\n }, (item: ItemModel) => item.id)\n }\n }\n}\n```\n\n### Component Reuse\n\n- Extract reusable components into separate files\n- Use `@Builder` for lightweight UI fragments within a component\n- Use `@Param` for configurable components\n\n## Resource References\n\nAlways define UI constants as resources and reference via `$r()`:\n\n```typescript\n// BAD: hardcoded values\nText('Hello')\n .fontSize(16)\n .fontColor('#333333')\n\n// GOOD: resource references\nText($r('app.string.greeting'))\n .fontSize($r('app.float.font_size_body'))\n .fontColor($r('app.color.text_primary'))\n```\n\n### arkts/security\n\n# HarmonyOS / ArkTS Security\n\n> This file extends [common/security.md](../common/security.md) with HarmonyOS-specific security practices.\n\n## Permission Management\n\n### Declare Permissions in module.json5\n\nAll system API calls requiring permissions must be declared:\n\n```json5\n{\n \"module\": {\n \"requestPermissions\": [\n {\n \"name\": \"ohos.permission.INTERNET\",\n \"reason\": \"$string:internet_permission_reason\",\n \"usedScene\": {\n \"abilities\": [\"EntryAbility\"],\n \"when\": \"always\"\n }\n }\n ]\n }\n}\n```\n\n### Permission Checklist\n\nBefore calling system APIs, verify:\n\n- [ ] Permission declared in `module.json5`\n- [ ] Permission reason string defined in resources (for user-facing permissions)\n- [ ] Runtime permission request implemented for sensitive permissions (camera, location, etc.)\n- [ ] Permission check before API call with graceful fallback on denial\n\n### Runtime Permission Request\n\n```typescript\nimport { abilityAccessCtrl, bundleManager, Permissions } from '@kit.AbilityKit';\n\nasync function checkAndRequestPermission(permission: Permissions): Promise<boolean> {\n const atManager = abilityAccessCtrl.createAtManager();\n const bundleInfo = await bundleManager.getBundleInfoForSelf(\n bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION\n );\n const tokenId = bundleInfo.appInfo.accessTokenId;\n const grantStatus = await atManager.checkAccessToken(tokenId, permission);\n\n if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {\n return true;\n }\n\n const result = await atManager.requestPermissionsFromUser(getContext(), [permission]);\n return result.authResults[0] === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;\n}\n```\n\n## Secret Management\n\n- **NEVER** hardcode API keys, tokens, or passwords in `.ets`/`.ts` source files\n- Use HarmonyOS Preferences API for non-sensitive configuration\n- Use HarmonyOS Keystore for sensitive credentials\n- Environment-specific configs should be managed via build profiles\n\n```typescript\n// BAD: hardcoded secret\nconst API_KEY: string = 'sk-xxxxxxxxxxxx';\n\n// GOOD: from build profile config (non-sensitive)\nimport { BuildProfile } from 'BuildProfile';\nconst endpoint = BuildProfile.API_ENDPOINT;\n\n// GOOD: use HUKS to encrypt/decrypt data without exposing key material\nimport { huks } from '@kit.UniversalKeystoreKit';\nasync function decryptWithKeystore(alias: string, nonce: Uint8Array, aad: Uint8Array, cipherData: Uint8Array): Promise<Uint8Array> {\n const options: huks.HuksOptions = {\n properties: [\n { tag: huks.HuksTag.HUKS_TAG_ALGORITHM, value: huks.HuksKeyAlg.HUKS_ALG_AES },\n { tag: huks.HuksTag.HUKS_TAG_PURPOSE, value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT },\n { tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, value: huks.HuksCipherMode.HUKS_MODE_GCM },\n { tag: huks.HuksTag.HUKS_TAG_PADDING, value: huks.HuksKeyPadding.HUKS_PADDING_NONE },\n { tag: huks.HuksTag.HUKS_TAG_NONCE, value: nonce },\n { tag: huks.HuksTag.HUKS_TAG_ASSOCIATED_DATA, value: aad }\n ],\n inData: cipherData\n };\n const handle = await huks.initSession(alias, options);\n const result = await huks.finishSession(handle.handle, options);\n return result.outData;\n}\n```\n\n## Input Validation\n\n- Validate all user input before processing\n- Sanitize data before displaying in UI to prevent injection\n- Validate deep link parameters before navigation\n\n```typescript\n// Validate before navigation\nfunction handleDeepLink(uri: string): void {\n const allowedPaths: string[] = ['detail', 'settings', 'profile'];\n const parsed = new URL(uri);\n const path = parsed.pathname.replace('/', '');\n\n if (!allowedPaths.includes(path)) {\n hilog.warn(0x0000, 'DeepLink', 'Invalid deep link path: %{public}s', path);\n return;\n }\n\n navPathStack.pushPath({ name: path });\n}\n```\n\n## Network Security\n\n- Always use HTTPS for network requests\n- Validate server certificates\n- Implement request timeout and retry policies\n- Never log sensitive data (tokens, user credentials) in network request/response logs\n\n## Data Storage Security\n\n- Use encrypted preferences for sensitive local data\n- Clear sensitive data from memory when no longer needed\n- Implement proper data lifecycle management\n- Consider data classification (public, internal, confidential) when choosing storage mechanisms\n\n## Dependency Security\n\n- Only use dependencies from trusted sources (official ohpm registry)\n- Verify dependency versions in `oh-package.json5`\n- Regularly check for known vulnerabilities in third-party libraries\n- Pin dependency versions to avoid unexpected updates\n\n### arkts/testing\n\n# HarmonyOS / ArkTS Testing\n\n> This file extends [common/testing.md](../common/testing.md) with HarmonyOS-specific testing practices.\n\n## Test Framework\n\nHarmonyOS uses the built-in test framework with `@ohos.test` capabilities:\n\n- **Unit tests**: Located in `src/ohosTest/ets/test/`\n- **UI tests**: Use `@ohos.UiTest` for component testing\n- **Instrument tests**: Run on device/emulator\n\n## Test Directory Structure\n\n```\nmodule/\n |-- src/\n | |-- main/ets/ # Production code\n | |-- ohosTest/ets/ # Test code\n | |-- test/\n | | |-- Ability.test.ets\n | | |-- List.test.ets\n | |-- TestAbility.ets\n | |-- TestRunner.ets\n```\n\n## Running Tests\n\n```bash\n# Run all tests for a module\nhvigorw testHap -p product=default\n\n# Run tests on connected device\nhdc shell aa test -b com.example.app -m entry_test -s unittest /ets/TestRunner/OpenHarmonyTestRunner\n```\n\n## Unit Test Example\n\n```typescript\nimport { describe, it, expect } from '@ohos/hypium';\n\nexport default function UserViewModelTest() {\n describe('UserViewModel', () => {\n it('should_initialize_with_empty_state', 0, () => {\n const vm = new UserViewModel();\n expect(vm.userName).assertEqual('');\n expect(vm.isLoading).assertFalse();\n });\n\n it('should_update_user_name', 0, () => {\n const vm = new UserViewModel();\n vm.updateUserName('Alice');\n expect(vm.userName).assertEqual('Alice');\n });\n\n it('should_handle_empty_input', 0, () => {\n const vm = new UserViewModel();\n vm.updateUserName('');\n expect(vm.userName).assertEqual('');\n expect(vm.hasError).assertFalse();\n });\n });\n}\n```\n\n## UI Test Example\n\n```typescript\nimport { describe, it, expect } from '@ohos/hypium';\nimport { Driver, ON } from '@ohos.UiTest';\n\nexport default function HomePageUITest() {\n describe('HomePage_UI', () => {\n it('should_display_title', 0, async () => {\n const driver = Driver.create();\n await driver.delayMs(1000);\n\n const title = await driver.findComponent(ON.text('Home'));\n expect(title !== null).assertTrue();\n });\n\n it('should_navigate_to_detail_on_click', 0, async () => {\n const driver = Driver.create();\n const button = await driver.findComponent(ON.id('detailButton'));\n await button.click();\n await driver.delayMs(500);\n\n const detailTitle = await driver.findComponent(ON.text('Detail'));\n expect(detailTitle !== null).assertTrue();\n });\n });\n}\n```\n\n## TDD Workflow for HarmonyOS\n\nFollow the standard TDD cycle adapted for HarmonyOS:\n\n1. **RED**: Write a failing test in `ohosTest/ets/test/`\n2. **GREEN**: Implement minimal code in `main/ets/` to pass\n3. **REFACTOR**: Clean up while keeping tests green\n4. **BUILD**: Run `hvigorw assembleHap` to verify compilation\n5. **VERIFY**: Run tests on device/emulator\n\n## Test Coverage Requirements\n\n- Minimum 80% coverage for all critical application code (ViewModels, services, utilities)\n- **Unit tests**: All utility functions, ViewModel logic, data models\n- **Integration tests**: API calls, database operations, cross-module interactions\n- **E2E / UI tests**: Critical user flows (login, navigation, data submission)\n- Test edge cases: empty data, network errors, permission denials\n\n## Testing Best Practices\n\n- Keep tests independent - no shared mutable state between tests\n- Mock network calls and system APIs in unit tests\n- Use meaningful test names: `should_[expected_behavior]_when_[condition]`\n- Test V2 state management reactivity: verify `@Trace` properties trigger UI updates\n- Test Navigation flows: verify `NavPathStack` push/pop/replace operations\n- Avoid testing framework internals - focus on business logic and user-visible behavior\n\n### common/agents\n\n# Agent Orchestration\n\n## Available Agents\n\nLocated in `~/.claude/agents/`:\n\n| Agent | Purpose | When to Use |\n|-------|---------|-------------|\n| planner | Implementation planning | Complex features, refactoring |\n| architect | System design | Architectural decisions |\n| tdd-guide | Test-driven development | New features, bug fixes |\n| code-reviewer | Code review | After writing code |\n| security-reviewer | Security analysis | Before commits |\n| build-error-resolver | Fix build errors | When build fails |\n| e2e-runner | E2E testing | Critical user flows |\n| refactor-cleaner | Dead code cleanup | Code maintenance |\n| doc-updater | Documentation | Updating docs |\n| rust-reviewer | Rust code review | Rust projects |\n| harmonyos-app-resolver | HarmonyOS app development | HarmonyOS/ArkTS projects |\n\n## Immediate Agent Usage\n\nNo user prompt needed:\n1. Complex feature requests - Use **planner** agent\n2. Code just written/modified - Use **code-reviewer** agent\n3. Bug fix or new feature - Use **tdd-guide** agent\n4. Architectural decision - Use **architect** agent\n\n## Parallel Task Execution\n\nALWAYS use parallel Task execution for independent operations:\n\n```markdown\n# GOOD: Parallel execution\nLaunch 3 agents in parallel:\n1. Agent 1: Security analysis of auth module\n2. Agent 2: Performance review of cache system\n3. Agent 3: Type checking of utilities\n\n# BAD: Sequential when unnecessary\nFirst agent 1, then agent 2, then agent 3\n```\n\n## Multi-Perspective Analysis\n\nFor complex problems, use split role sub-agents:\n- Factual reviewer\n- Senior engineer\n- Security expert\n- Consistency reviewer\n- Redundancy checker\n\n### common/code-review\n\n# Code Review Standards\n\n## Purpose\n\nCode review ensures quality, security, and maintainability before code is merged. This rule defines when and how to conduct code reviews.\n\n## When to Review\n\n**MANDATORY review triggers:**\n\n- After writing or modifying code\n- Before any commit to shared branches\n- When security-sensitive code is changed (auth, payments, user data)\n- When architectural changes are made\n- Before merging pull requests\n\n**Pre-Review Requirements:**\n\nBefore requesting review, ensure:\n\n- All automated checks (CI/CD) are passing\n- Merge conflicts are resolved\n- Branch is up to date with target branch\n\n## Review Checklist\n\nBefore marking code complete:\n\n- [ ] Code is readable and well-named\n- [ ] Functions are focused (<50 lines)\n- [ ] Files are cohesive (<800 lines)\n- [ ] No deep nesting (>4 levels)\n- [ ] Errors are handled explicitly\n- [ ] No hardcoded secrets or credentials\n- [ ] No console.log or debug statements\n- [ ] Tests exist for new functionality\n- [ ] Test coverage meets 80% minimum\n\n## Security Review Triggers\n\n**STOP and use security-reviewer agent when:**\n\n- Authentication or authorization code\n- User input handling\n- Database queries\n- File system operations\n- External API calls\n- Cryptographic operations\n- Payment or financial code\n\n## Review Severity Levels\n\n| Level | Meaning | Action |\n|-------|---------|--------|\n| CRITICAL | Security vulnerability or data loss risk | **BLOCK** - Must fix before merge |\n| HIGH | Bug or significant quality issue | **WARN** - Should fix before merge |\n| MEDIUM | Maintainability concern | **INFO** - Consider fixing |\n| LOW | Style or minor suggestion | **NOTE** - Optional |\n\n## Agent Usage\n\nUse these agents for code review:\n\n| Agent | Purpose |\n|-------|---------|\n| **code-reviewer** | General code quality, patterns, best practices |\n| **security-reviewer** | Security vulnerabilities, OWASP Top 10 |\n| **typescript-reviewer** | TypeScript/JavaScript specific issues |\n| **python-reviewer** | Python specific issues |\n| **go-reviewer** | Go specific issues |\n| **rust-reviewer** | Rust specific issues |\n\n## Review Workflow\n\n```\n1. Run git diff to understand changes\n2. Check security checklist first\n3. Review code quality checklist\n4. Run relevant tests\n5. Verify coverage >= 80%\n6. Use appropriate agent for detailed review\n```\n\n## Common Issues to Catch\n\n### Security\n\n- Hardcoded credentials (API keys, passwords, tokens)\n- SQL injection (string concatenation in queries)\n- XSS vulnerabilities (unescaped user input)\n- Path traversal (unsanitized file paths)\n- CSRF protection missing\n- Authentication bypasses\n\n### Code Quality\n\n- Large functions (>50 lines) - split into smaller\n- Large files (>800 lines) - extract modules\n- Deep nesting (>4 levels) - use early returns\n- Missing error handling - handle explicitly\n- Mutation patterns - prefer immutable operations\n- Missing tests - add test coverage\n\n### Performance\n\n- N+1 queries - use JOINs or batching\n- Missing pagination - add LIMIT to queries\n- Unbounded queries - add constraints\n- Missing caching - cache expensive operations\n\n## Approval Criteria\n\n- **Approve**: No CRITICAL or HIGH issues\n- **Warning**: Only HIGH issues (merge with caution)\n- **Block**: CRITICAL issues found\n\n## Integration with Other Rules\n\nThis rule works with:\n\n- [testing.md](testing.md) - Test coverage requirements\n- [security.md](security.md) - Security checklist\n- [git-workflow.md](git-workflow.md) - Commit standards\n- [agents.md](agents.md) - Agent delegation\n\n### common/coding-style\n\n# Coding Style\n\n## Immutability (CRITICAL)\n\nALWAYS create new objects, NEVER mutate existing ones:\n\n```\n// Pseudocode\nWRONG: modify(original, field, value) → changes original in-place\nCORRECT: update(original, field, value) → returns new copy with change\n```\n\nRationale: Immutable data prevents hidden side effects, makes debugging easier, and enables safe concurrency.\n\n## Core Principles\n\n### KISS (Keep It Simple)\n\n- Prefer the simplest solution that actually works\n- Avoid premature optimization\n- Optimize for clarity over cleverness\n\n### DRY (Don't Repeat Yourself)\n\n- Extract repeated logic into shared functions or utilities\n- Avoid copy-paste implementation drift\n- Introduce abstractions when repetition is real, not speculative\n\n### YAGNI (You Aren't Gonna Need It)\n\n- Do not build features or abstractions before they are needed\n- Avoid speculative generality\n- Start simple, then refactor when the pressure is real\n\n## File Organization\n\nMANY SMALL FILES > FEW LARGE FILES:\n- High cohesion, low coupling\n- 200-400 lines typical, 800 max\n- Extract utilities from large modules\n- Organize by feature/domain, not by type\n\n## Error Handling\n\nALWAYS handle errors comprehensively:\n- Handle errors explicitly at every level\n- Provide user-friendly error messages in UI-facing code\n- Log detailed error context on the server side\n- Never silently swallow errors\n\n## Input Validation\n\nALWAYS validate at system boundaries:\n- Validate all user input before processing\n- Use schema-based validation where available\n- Fail fast with clear error messages\n- Never trust external data (API responses, user input, file content)\n\n## Naming Conventions\n\n- Variables and functions: `camelCase` with descriptive names\n- Booleans: prefer `is`, `has`, `should`, or `can` prefixes\n- Interfaces, types, and components: `PascalCase`\n- Constants: `UPPER_SNAKE_CASE`\n- Custom hooks: `camelCase` with a `use` prefix\n\n## Code Smells to Avoid\n\n### Deep Nesting\n\nPrefer early returns over nested conditionals once the logic starts stacking.\n\n### Magic Numbers\n\nUse named constants for meaningful thresholds, delays, and limits.\n\n### Long Functions\n\nSplit large functions into focused pieces with clear responsibilities.\n\n## Code Quality Checklist\n\nBefore marking work complete:\n- [ ] Code is readable and well-named\n- [ ] Functions are small (<50 lines)\n- [ ] Files are focused (<800 lines)\n- [ ] No deep nesting (>4 levels)\n- [ ] Proper error handling\n- [ ] No hardcoded values (use constants or config)\n- [ ] No mutation (immutable patterns used)\n\n### common/development-workflow\n\n# Development Workflow\n\n> This file extends [common/git-workflow.md](./git-workflow.md) with the full feature development process that happens before git operations.\n\nThe Feature Implementation Workflow describes the development pipeline: research, planning, TDD, code review, and then committing to git.\n\n## Feature Implementation Workflow\n\n0. **Research & Reuse** _(mandatory before any new implementation)_\n - **GitHub code search first:** Run `gh search repos` and `gh search code` to find existing implementations, templates, and patterns before writing anything new.\n - **Library docs second:** Use Context7 or primary vendor docs to confirm API behavior, package usage, and version-specific details before implementing.\n - **Exa only when the first two are insufficient:** Use Exa for broader web research or discovery after GitHub search and primary docs.\n - **Check package registries:** Search npm, PyPI, crates.io, and other registries before writing utility code. Prefer battle-tested libraries over hand-rolled solutions.\n - **Search for adaptable implementations:** Look for open-source projects that solve 80%+ of the problem and can be forked, ported, or wrapped.\n - Prefer adopting or porting a proven approach over writing net-new code when it meets the requirement.\n\n1. **Plan First**\n - Use **planner** agent to create implementation plan\n - Generate planning docs before coding: PRD, architecture, system_design, tech_doc, task_list\n - Identify dependencies and risks\n - Break down into phases\n\n2. **TDD Approach**\n - Use **tdd-guide** agent\n - Write tests first (RED)\n - Implement to pass tests (GREEN)\n - Refactor (IMPROVE)\n - Verify 80%+ coverage\n\n3. **Code Review**\n - Use **code-reviewer** agent immediately after writing code\n - Address CRITICAL and HIGH issues\n - Fix MEDIUM issues when possible\n\n4. **Commit & Push**\n - Detailed commit messages\n - Follow conventional commits format\n - See [git-workflow.md](./git-workflow.md) for commit message format and PR process\n\n5. **Pre-Review Checks**\n - Verify all automated checks (CI/CD) are passing\n - Resolve any merge conflicts\n - Ensure branch is up to date with target branch\n - Only request review after these checks pass\n\n### common/git-workflow\n\n# Git Workflow\n\n## Commit Message Format\n```\n<type>: <description>\n\n<optional body>\n```\n\nTypes: feat, fix, refactor, docs, test, chore, perf, ci\n\nNote: To disable co-author attribution on commits, set `\"includeCoAuthoredBy\": false` in `~/.claude/settings.json` (Claude Code appends `Co-Authored-By` by default; ECC does not ship this setting).\n\n## Pull Request Workflow\n\nWhen creating PRs:\n1. Analyze full commit history (not just latest commit)\n2. Use `git diff [base-branch]...HEAD` to see all changes\n3. Draft comprehensive PR summary\n4. Include test plan with TODOs\n5. Push with `-u` flag if new branch\n\n> For the full development process (planning, TDD, code review) before git operations,\n> see [development-workflow.md](./development-workflow.md).\n\n### common/hooks\n\n# Hooks System\n\n## Hook Types\n\n- **PreToolUse**: Before tool execution (validation, parameter modification)\n- **PostToolUse**: After tool execution (auto-format, checks)\n- **Stop**: When session ends (final verification)\n\n## Auto-Accept Permissions\n\nUse with caution:\n- Enable for trusted, well-defined plans\n- Disable for exploratory work\n- Never use dangerously-skip-permissions flag\n- Configure `allowedTools` in `~/.claude.json` instead\n\n## TodoWrite Best Practices\n\nUse TodoWrite tool to:\n- Track progress on multi-step tasks\n- Verify understanding of instructions\n- Enable real-time steering\n- Show granular implementation steps\n\nTodo list reveals:\n- Out of order steps\n- Missing items\n- Extra unnecessary items\n- Wrong granularity\n- Misinterpreted requirements\n\n### common/patterns\n\n# Common Patterns\n\n## Skeleton Projects\n\nWhen implementing new functionality:\n1. Search for battle-tested skeleton projects\n2. Use parallel agents to evaluate options:\n - Security assessment\n - Extensibility analysis\n - Relevance scoring\n - Implementation planning\n3. Clone best match as foundation\n4. Iterate within proven structure\n\n## Design Patterns\n\n### Repository Pattern\n\nEncapsulate data access behind a consistent interface:\n- Define standard operations: findAll, findById, create, update, delete\n- Concrete implementations handle storage details (database, API, file, etc.)\n- Business logic depends on the abstract interface, not the storage mechanism\n- Enables easy swapping of data sources and simplifies testing with mocks\n\n### API Response Format\n\nUse a consistent envelope for all API responses:\n- Include a success/status indicator\n- Include the data payload (nullable on error)\n- Include an error message field (nullable on success)\n- Include metadata for paginated responses (total, page, limit)\n\n### common/performance\n\n# Performance Optimization\n\n## Model Selection Strategy\n\n**Haiku** (90% of Sonnet capability, 3x cost savings):\n- Lightweight agents with frequent invocation\n- Pair programming and code generation\n- Worker agents in multi-agent systems\n\n**Sonnet** (Best coding model):\n- Main development work\n- Orchestrating multi-agent workflows\n- Complex coding tasks\n\n**Opus** (Deepest reasoning):\n- Complex architectural decisions\n- Maximum reasoning requirements\n- Research and analysis tasks\n\n## Context Window Management\n\nAvoid last 20% of context window for:\n- Large-scale refactoring\n- Feature implementation spanning multiple files\n- Debugging complex interactions\n\nLower context sensitivity tasks:\n- Single-file edits\n- Independent utility creation\n- Documentation updates\n- Simple bug fixes\n\n## Extended Thinking + Plan Mode\n\nExtended thinking is enabled by default, reserving up to 31,999 tokens for internal reasoning.\n\nControl extended thinking via:\n- **Toggle**: Option+T (macOS) / Alt+T (Windows/Linux)\n- **Config**: Set `alwaysThinkingEnabled` in `~/.claude/settings.json`\n- **Budget cap**: `export MAX_THINKING_TOKENS=10000` (bash) or `$env:MAX_THINKING_TOKENS = \"10000\"` (PowerShell)\n- **Verbose mode**: Ctrl+O to see thinking output\n\nFor complex tasks requiring deep reasoning:\n1. Ensure extended thinking is enabled (on by default)\n2. Enable **Plan Mode** for structured approach\n3. Use multiple critique rounds for thorough analysis\n4. Use split role sub-agents for diverse perspectives\n\n## Build Troubleshooting\n\nIf build fails:\n1. Use **build-error-resolver** agent\n2. Analyze error messages\n3. Fix incrementally\n4. Verify after each fix\n\n### common/security\n\n# Security Guidelines\n\n## Mandatory Security Checks\n\nBefore ANY commit:\n- [ ] No hardcoded secrets (API keys, passwords, tokens)\n- [ ] All user inputs validated\n- [ ] SQL injection prevention (parameterized queries)\n- [ ] XSS prevention (sanitized HTML)\n- [ ] CSRF protection enabled\n- [ ] Authentication/authorization verified\n- [ ] Rate limiting on all endpoints\n- [ ] Error messages don't leak sensitive data\n\n## Secret Management\n\n- NEVER hardcode secrets in source code\n- ALWAYS use environment variables or a secret manager\n- Validate that required secrets are present at startup\n- Rotate any secrets that may have been exposed\n\n## Security Response Protocol\n\nIf security issue found:\n1. STOP immediately\n2. Use **security-reviewer** agent\n3. Fix CRITICAL issues before continuing\n4. Rotate any exposed secrets\n5. Review entire codebase for similar issues\n\n### common/testing\n\n# Testing Requirements\n\n## Minimum Test Coverage: 80%\n\nTest Types (ALL required):\n1. **Unit Tests** - Individual functions, utilities, components\n2. **Integration Tests** - API endpoints, database operations\n3. **E2E Tests** - Critical user flows (framework chosen per language)\n\n## Test-Driven Development\n\nMANDATORY workflow:\n1. Write test first (RED)\n2. Run test - it should FAIL\n3. Write minimal implementation (GREEN)\n4. Run test - it should PASS\n5. Refactor (IMPROVE)\n6. Verify coverage (80%+)\n\n## Troubleshooting Test Failures\n\n1. Use **tdd-guide** agent\n2. Check test isolation\n3. Verify mocks are correct\n4. Fix implementation, not tests (unless tests are wrong)\n\n## Agent Support\n\n- **tdd-guide** - Use PROACTIVELY for new features, enforces write-tests-first\n\n## Test Structure (AAA Pattern)\n\nPrefer Arrange-Act-Assert structure for tests:\n\n```typescript\ntest('calculates similarity correctly', () => {\n // Arrange\n const vector1 = [1, 0, 0]\n const vector2 = [0, 1, 0]\n\n // Act\n const similarity = calculateCosineSimilarity(vector1, vector2)\n\n // Assert\n expect(similarity).toBe(0)\n})\n```\n\n### Test Naming\n\nUse descriptive names that explain the behavior under test:\n\n```typescript\ntest('returns empty array when no markets match query', () => {})\ntest('throws error when API key is missing', () => {})\ntest('falls back to substring search when Redis is unavailable', () => {})\n```\n\n### cpp/coding-style\n\n# C++ Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with C++ specific content.\n\n## Modern C++ (C++17/20/23)\n\n- Prefer **modern C++ features** over C-style constructs\n- Use `auto` when the type is obvious from context\n- Use `constexpr` for compile-time constants\n- Use structured bindings: `auto [key, value] = map_entry;`\n\n## Resource Management\n\n- **RAII everywhere** — no manual `new`/`delete`\n- Use `std::unique_ptr` for exclusive ownership\n- Use `std::shared_ptr` only when shared ownership is truly needed\n- Use `std::make_unique` / `std::make_shared` over raw `new`\n\n## Naming Conventions\n\n- Types/Classes: `PascalCase`\n- Functions/Methods: `snake_case` or `camelCase` (follow project convention)\n- Constants: `kPascalCase` or `UPPER_SNAKE_CASE`\n- Namespaces: `lowercase`\n- Member variables: `snake_case_` (trailing underscore) or `m_` prefix\n\n## Formatting\n\n- Use **clang-format** — no style debates\n- Run `clang-format -i <file>` before committing\n\n## Reference\n\nSee skill: `cpp-coding-standards` for comprehensive C++ coding standards and guidelines.\n\n### cpp/hooks\n\n# C++ Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with C++ specific content.\n\n## Build Hooks\n\nRun these checks before committing C++ changes:\n\n```bash\n# Format check\nclang-format --dry-run --Werror src/*.cpp src/*.hpp\n\n# Static analysis\nclang-tidy src/*.cpp -- -std=c++17\n\n# Build\ncmake --build build\n\n# Tests\nctest --test-dir build --output-on-failure\n```\n\n## Recommended CI Pipeline\n\n1. **clang-format** — formatting check\n2. **clang-tidy** — static analysis\n3. **cppcheck** — additional analysis\n4. **cmake build** — compilation\n5. **ctest** — test execution with sanitizers\n\n### cpp/patterns\n\n# C++ Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with C++ specific content.\n\n## RAII (Resource Acquisition Is Initialization)\n\nTie resource lifetime to object lifetime:\n\n```cpp\nclass FileHandle {\npublic:\n explicit FileHandle(const std::string& path) : file_(std::fopen(path.c_str(), \"r\")) {}\n ~FileHandle() { if (file_) std::fclose(file_); }\n FileHandle(const FileHandle&) = delete;\n FileHandle& operator=(const FileHandle&) = delete;\nprivate:\n std::FILE* file_;\n};\n```\n\n## Rule of Five/Zero\n\n- **Rule of Zero**: Prefer classes that need no custom destructor, copy/move constructors, or assignments\n- **Rule of Five**: If you define any of destructor/copy-ctor/copy-assign/move-ctor/move-assign, define all five\n\n## Value Semantics\n\n- Pass small/trivial types by value\n- Pass large types by `const&`\n- Return by value (rely on RVO/NRVO)\n- Use move semantics for sink parameters\n\n## Error Handling\n\n- Use exceptions for exceptional conditions\n- Use `std::optional` for values that may not exist\n- Use `std::expected` (C++23) or result types for expected failures\n\n## Reference\n\nSee skill: `cpp-coding-standards` for comprehensive C++ patterns and anti-patterns.\n\n### cpp/security\n\n# C++ Security\n\n> This file extends [common/security.md](../common/security.md) with C++ specific content.\n\n## Memory Safety\n\n- Never use raw `new`/`delete` — use smart pointers\n- Never use C-style arrays — use `std::array` or `std::vector`\n- Never use `malloc`/`free` — use C++ allocation\n- Avoid `reinterpret_cast` unless absolutely necessary\n\n## Buffer Overflows\n\n- Use `std::string` over `char*`\n- Use `.at()` for bounds-checked access when safety matters\n- Never use `strcpy`, `strcat`, `sprintf` — use `std::string` or `fmt::format`\n\n## Undefined Behavior\n\n- Always initialize variables\n- Avoid signed integer overflow\n- Never dereference null or dangling pointers\n- Use sanitizers in CI:\n ```bash\n cmake -DCMAKE_CXX_FLAGS=\"-fsanitize=address,undefined\" ..\n ```\n\n## Static Analysis\n\n- Use **clang-tidy** for automated checks:\n ```bash\n clang-tidy --checks='*' src/*.cpp\n ```\n- Use **cppcheck** for additional analysis:\n ```bash\n cppcheck --enable=all src/\n ```\n\n## Reference\n\nSee skill: `cpp-coding-standards` for detailed security guidelines.\n\n### cpp/testing\n\n# C++ Testing\n\n> This file extends [common/testing.md](../common/testing.md) with C++ specific content.\n\n## Framework\n\nUse **GoogleTest** (gtest/gmock) with **CMake/CTest**.\n\n## Running Tests\n\n```bash\ncmake --build build && ctest --test-dir build --output-on-failure\n```\n\n## Coverage\n\n```bash\ncmake -DCMAKE_CXX_FLAGS=\"--coverage\" -DCMAKE_EXE_LINKER_FLAGS=\"--coverage\" ..\ncmake --build .\nctest --output-on-failure\nlcov --capture --directory . --output-file coverage.info\n```\n\n## Sanitizers\n\nAlways run tests with sanitizers in CI:\n\n```bash\ncmake -DCMAKE_CXX_FLAGS=\"-fsanitize=address,undefined\" ..\n```\n\n## Reference\n\nSee skill: `cpp-testing` for detailed C++ testing patterns, TDD workflow, and GoogleTest/GMock usage.\n\n### csharp/coding-style\n\n# C# Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with C#-specific content.\n\n## Standards\n\n- Follow current .NET conventions and enable nullable reference types\n- Prefer explicit access modifiers on public and internal APIs\n- Keep files aligned with the primary type they define\n\n## Types and Models\n\n- Prefer `record` or `record struct` for immutable value-like models\n- Use `class` for entities or types with identity and lifecycle\n- Use `interface` for service boundaries and abstractions\n- Avoid `dynamic` in application code; prefer generics or explicit models\n\n```csharp\npublic sealed record UserDto(Guid Id, string Email);\n\npublic interface IUserRepository\n{\n Task<UserDto?> FindByIdAsync(Guid id, CancellationToken cancellationToken);\n}\n```\n\n## Immutability\n\n- Prefer `init` setters, constructor parameters, and immutable collections for shared state\n- Do not mutate input models in-place when producing updated state\n\n```csharp\npublic sealed record UserProfile(string Name, string Email);\n\npublic static UserProfile Rename(UserProfile profile, string name) =>\n profile with { Name = name };\n```\n\n## Async and Error Handling\n\n- Prefer `async`/`await` over blocking calls like `.Result` or `.Wait()`\n- Pass `CancellationToken` through public async APIs\n- Throw specific exceptions and log with structured properties\n\n```csharp\npublic async Task<Order> LoadOrderAsync(\n Guid orderId,\n CancellationToken cancellationToken)\n{\n try\n {\n return await repository.FindAsync(orderId, cancellationToken)\n ?? throw new InvalidOperationException($\"Order {orderId} was not found.\");\n }\n catch (Exception ex)\n {\n logger.LogError(ex, \"Failed to load order {OrderId}\", orderId);\n throw;\n }\n}\n```\n\n## Formatting\n\n- Use `dotnet format` for formatting and analyzer fixes\n- Keep `using` directives organized and remove unused imports\n- Prefer expression-bodied members only when they stay readable\n\n### csharp/hooks\n\n# C# Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with C#-specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **dotnet format**: Auto-format edited C# files and apply analyzer fixes\n- **dotnet build**: Verify the solution or project still compiles after edits\n- **dotnet test --no-build**: Re-run the nearest relevant test project after behavior changes\n\n## Stop Hooks\n\n- Run a final `dotnet build` before ending a session with broad C# changes\n- Warn on modified `appsettings*.json` files so secrets do not get committed\n\n### csharp/patterns\n\n# C# Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with C#-specific content.\n\n## API Response Pattern\n\n```csharp\npublic sealed record ApiResponse<T>(\n bool Success,\n T? Data = default,\n string? Error = null,\n object? Meta = null);\n```\n\n## Repository Pattern\n\n```csharp\npublic interface IRepository<T>\n{\n Task<IReadOnlyList<T>> FindAllAsync(CancellationToken cancellationToken);\n Task<T?> FindByIdAsync(Guid id, CancellationToken cancellationToken);\n Task<T> CreateAsync(T entity, CancellationToken cancellationToken);\n Task<T> UpdateAsync(T entity, CancellationToken cancellationToken);\n Task DeleteAsync(Guid id, CancellationToken cancellationToken);\n}\n```\n\n## Options Pattern\n\nUse strongly typed options for config instead of reading raw strings throughout the codebase.\n\n```csharp\npublic sealed class PaymentsOptions\n{\n public const string SectionName = \"Payments\";\n public required string BaseUrl { get; init; }\n public required string ApiKeySecretName { get; init; }\n}\n```\n\n## Dependency Injection\n\n- Depend on interfaces at service boundaries\n- Keep constructors focused; if a service needs too many dependencies, split responsibilities\n- Register lifetimes intentionally: singleton for stateless/shared services, scoped for request data, transient for lightweight pure workers\n\n### csharp/security\n\n# C# Security\n\n> This file extends [common/security.md](../common/security.md) with C#-specific content.\n\n## Secret Management\n\n- Never hardcode API keys, tokens, or connection strings in source code\n- Use environment variables, user secrets for local development, and a secret manager in production\n- Keep `appsettings.*.json` free of real credentials\n\n```csharp\n// BAD\nconst string ApiKey = \"sk-live-123\";\n\n// GOOD\nvar apiKey = builder.Configuration[\"OpenAI:ApiKey\"]\n ?? throw new InvalidOperationException(\"OpenAI:ApiKey is not configured.\");\n```\n\n## SQL Injection Prevention\n\n- Always use parameterized queries with ADO.NET, Dapper, or EF Core\n- Never concatenate user input into SQL strings\n- Validate sort fields and filter operators before using dynamic query composition\n\n```csharp\nconst string sql = \"SELECT * FROM Orders WHERE CustomerId = @customerId\";\nawait connection.QueryAsync<Order>(sql, new { customerId });\n```\n\n## Input Validation\n\n- Validate DTOs at the application boundary\n- Use data annotations, FluentValidation, or explicit guard clauses\n- Reject invalid model state before running business logic\n\n## Authentication and Authorization\n\n- Prefer framework auth handlers instead of custom token parsing\n- Enforce authorization policies at endpoint or handler boundaries\n- Never log raw tokens, passwords, or PII\n\n## Error Handling\n\n- Return safe client-facing messages\n- Log detailed exceptions with structured context server-side\n- Do not expose stack traces, SQL text, or filesystem paths in API responses\n\n## References\n\nSee skill: `security-review` for broader application security review checklists.\n\n### csharp/testing\n\n# C# Testing\n\n> This file extends [common/testing.md](../common/testing.md) with C#-specific content.\n\n## Test Framework\n\n- Prefer **xUnit** for unit and integration tests\n- Use **FluentAssertions** for readable assertions\n- Use **Moq** or **NSubstitute** for mocking dependencies\n- Use **Testcontainers** when integration tests need real infrastructure\n\n## Test Organization\n\n- Mirror `src/` structure under `tests/`\n- Separate unit, integration, and end-to-end coverage clearly\n- Name tests by behavior, not implementation details\n\n```csharp\npublic sealed class OrderServiceTests\n{\n [Fact]\n public async Task FindByIdAsync_ReturnsOrder_WhenOrderExists()\n {\n // Arrange\n // Act\n // Assert\n }\n}\n```\n\n## ASP.NET Core Integration Tests\n\n- Use `WebApplicationFactory<TEntryPoint>` for API integration coverage\n- Test auth, validation, and serialization through HTTP, not by bypassing middleware\n\n## Coverage\n\n- Target 80%+ line coverage\n- Focus coverage on domain logic, validation, auth, and failure paths\n- Run `dotnet test` in CI with coverage collection enabled where available\n\n### dart/coding-style\n\n# Dart/Flutter Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Dart and Flutter-specific content.\n\n## Formatting\n\n- **dart format** for all `.dart` files — enforced in CI (`dart format --set-exit-if-changed .`)\n- Line length: 80 characters (dart format default)\n- Trailing commas on multi-line argument/parameter lists to improve diffs and formatting\n\n## Immutability\n\n- Prefer `final` for local variables and `const` for compile-time constants\n- Use `const` constructors wherever all fields are `final`\n- Return unmodifiable collections from public APIs (`List.unmodifiable`, `Map.unmodifiable`)\n- Use `copyWith()` for state mutations in immutable state classes\n\n```dart\n// BAD\nvar count = 0;\nList<String> items = ['a', 'b'];\n\n// GOOD\nfinal count = 0;\nconst items = ['a', 'b'];\n```\n\n## Naming\n\nFollow Dart conventions:\n- `camelCase` for variables, parameters, and named constructors\n- `PascalCase` for classes, enums, typedefs, and extensions\n- `snake_case` for file names and library names\n- `SCREAMING_SNAKE_CASE` for constants declared with `const` at top level\n- Prefix private members with `_`\n- Extension names describe the type they extend: `StringExtensions`, not `MyHelpers`\n\n## Null Safety\n\n- Avoid `!` (bang operator) — prefer `?.`, `??`, `if (x != null)`, or Dart 3 pattern matching; reserve `!` only where a null value is a programming error and crashing is the right behaviour\n- Avoid `late` unless initialization is guaranteed before first use (prefer nullable or constructor init)\n- Use `required` for constructor parameters that must always be provided\n\n```dart\n// BAD — crashes at runtime if user is null\nfinal name = user!.name;\n\n// GOOD — null-aware operators\nfinal name = user?.name ?? 'Unknown';\n\n// GOOD — Dart 3 pattern matching (exhaustive, compiler-checked)\nfinal name = switch (user) {\n User(:final name) => name,\n null => 'Unknown',\n};\n\n// GOOD — early-return null guard\nString getUserName(User? user) {\n if (user == null) return 'Unknown';\n return user.name; // promoted to non-null after the guard\n}\n```\n\n## Sealed Types and Pattern Matching (Dart 3+)\n\nUse sealed classes to model closed state hierarchies:\n\n```dart\nsealed class AsyncState<T> {\n const AsyncState();\n}\n\nfinal class Loading<T> extends AsyncState<T> {\n const Loading();\n}\n\nfinal class Success<T> extends AsyncState<T> {\n const Success(this.data);\n final T data;\n}\n\nfinal class Failure<T> extends AsyncState<T> {\n const Failure(this.error);\n final Object error;\n}\n```\n\nAlways use exhaustive `switch` with sealed types — no default/wildcard:\n\n```dart\n// BAD\nif (state is Loading) { ... }\n\n// GOOD\nreturn switch (state) {\n Loading() => const CircularProgressIndicator(),\n Success(:final data) => DataWidget(data),\n Failure(:final error) => ErrorWidget(error.toString()),\n};\n```\n\n## Error Handling\n\n- Specify exception types in `on` clauses — never use bare `catch (e)`\n- Never catch `Error` subtypes — they indicate programming bugs\n- Use `Result`-style types or sealed classes for recoverable errors\n- Avoid using exceptions for control flow\n\n```dart\n// BAD\ntry {\n await fetchUser();\n} catch (e) {\n log(e.toString());\n}\n\n// GOOD\ntry {\n await fetchUser();\n} on NetworkException catch (e) {\n log('Network error: ${e.message}');\n} on NotFoundException {\n handleNotFound();\n}\n```\n\n## Async / Futures\n\n- Always `await` Futures or explicitly call `unawaited()` to signal intentional fire-and-forget\n- Never mark a function `async` if it never `await`s anything\n- Use `Future.wait` / `Future.any` for concurrent operations\n- Check `context.mounted` before using `BuildContext` after any `await` (Flutter 3.7+)\n\n```dart\n// BAD — ignoring Future\nfetchData(); // fire-and-forget without marking intent\n\n// GOOD\nunawaited(fetchData()); // explicit fire-and-forget\nawait fetchData(); // or properly awaited\n```\n\n## Imports\n\n- Use `package:` imports throughout — never relative imports (`../`) for cross-feature or cross-layer code\n- Order: `dart:` → external `package:` → internal `package:` (same package)\n- No unused imports — `dart analyze` enforces this with `unused_import`\n\n## Code Generation\n\n- Generated files (`.g.dart`, `.freezed.dart`, `.gr.dart`) must be committed or gitignored consistently — pick one strategy per project\n- Never manually edit generated files\n- Keep generator annotations (`@JsonSerializable`, `@freezed`, `@riverpod`, etc.) on the canonical source file only\n\n### dart/hooks\n\n# Dart/Flutter Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Dart and Flutter-specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **dart format**: Auto-format `.dart` files after edit\n- **dart analyze**: Run static analysis after editing Dart files and surface warnings\n- **flutter test**: Optionally run affected tests after significant changes\n\n## Recommended Hook Configuration\n\n```json\n{\n \"hooks\": {\n \"PostToolUse\": [\n {\n \"matcher\": { \"tool_name\": \"Edit\", \"file_paths\": [\"**/*.dart\"] },\n \"hooks\": [\n { \"type\": \"command\", \"command\": \"dart format $CLAUDE_FILE_PATHS\" }\n ]\n }\n ]\n }\n}\n```\n\n## Pre-commit Checks\n\nRun before committing Dart/Flutter changes:\n\n```bash\ndart format --set-exit-if-changed .\ndart analyze --fatal-infos\nflutter test\n```\n\n## Useful One-liners\n\n```bash\n# Format all Dart files\ndart format .\n\n# Analyze and report issues\ndart analyze\n\n# Run all tests with coverage\nflutter test --coverage\n\n# Regenerate code-gen files\ndart run build_runner build --delete-conflicting-outputs\n\n# Check for outdated packages\nflutter pub outdated\n\n# Upgrade packages within constraints\nflutter pub upgrade\n```\n\n### dart/patterns\n\n# Dart/Flutter Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Dart, Flutter, and common ecosystem-specific content.\n\n## Repository Pattern\n\n```dart\nabstract interface class UserRepository {\n Future<User?> getById(String id);\n Future<List<User>> getAll();\n Stream<List<User>> watchAll();\n Future<void> save(User user);\n Future<void> delete(String id);\n}\n\nclass UserRepositoryImpl implements UserRepository {\n const UserRepositoryImpl(this._remote, this._local);\n\n final UserRemoteDataSource _remote;\n final UserLocalDataSource _local;\n\n @override\n Future<User?> getById(String id) async {\n final local = await _local.getById(id);\n if (local != null) return local;\n final remote = await _remote.getById(id);\n if (remote != null) await _local.save(remote);\n return remote;\n }\n\n @override\n Future<List<User>> getAll() async {\n final remote = await _remote.getAll();\n for (final user in remote) {\n await _local.save(user);\n }\n return remote;\n }\n\n @override\n Stream<List<User>> watchAll() => _local.watchAll();\n\n @override\n Future<void> save(User user) => _local.save(user);\n\n @override\n Future<void> delete(String id) async {\n await _remote.delete(id);\n await _local.delete(id);\n }\n}\n```\n\n## State Management: BLoC/Cubit\n\n```dart\n// Cubit — simple state transitions\nclass CounterCubit extends Cubit<int> {\n CounterCubit() : super(0);\n\n void increment() => emit(state + 1);\n void decrement() => emit(state - 1);\n}\n\n// BLoC — event-driven\n@immutable\nsealed class CartEvent {}\nclass CartItemAdded extends CartEvent { CartItemAdded(this.item); final Item item; }\nclass CartItemRemoved extends CartEvent { CartItemRemoved(this.id); final String id; }\nclass CartCleared extends CartEvent {}\n\n@immutable\nclass CartState {\n const CartState({this.items = const []});\n final List<Item> items;\n CartState copyWith({List<Item>? items}) => CartState(items: items ?? this.items);\n}\n\nclass CartBloc extends Bloc<CartEvent, CartState> {\n CartBloc() : super(const CartState()) {\n on<CartItemAdded>((event, emit) =>\n emit(state.copyWith(items: [...state.items, event.item])));\n on<CartItemRemoved>((event, emit) =>\n emit(state.copyWith(items: state.items.where((i) => i.id != event.id).toList())));\n on<CartCleared>((_, emit) => emit(const CartState()));\n }\n}\n```\n\n## State Management: Riverpod\n\n```dart\n// Simple provider\n@riverpod\nFuture<List<User>> users(Ref ref) async {\n final repo = ref.watch(userRepositoryProvider);\n return repo.getAll();\n}\n\n// Notifier for mutable state\n@riverpod\nclass CartNotifier extends _$CartNotifier {\n @override\n List<Item> build() => [];\n\n void add(Item item) => state = [...state, item];\n void remove(String id) => state = state.where((i) => i.id != id).toList();\n void clear() => state = [];\n}\n\n// ConsumerWidget\nclass CartPage extends ConsumerWidget {\n const CartPage({super.key});\n\n @override\n Widget build(BuildContext context, WidgetRef ref) {\n final items = ref.watch(cartNotifierProvider);\n return ListView(\n children: items.map((item) => CartItemTile(item: item)).toList(),\n );\n }\n}\n```\n\n## Dependency Injection\n\nConstructor injection is preferred. Use `get_it` or Riverpod providers at composition root:\n\n```dart\n// get_it registration (in a setup file)\nvoid setupDependencies() {\n final di = GetIt.instance;\n di.registerSingleton<ApiClient>(ApiClient(baseUrl: Env.apiUrl));\n di.registerSingleton<UserRepository>(\n UserRepositoryImpl(di<ApiClient>(), di<LocalDatabase>()),\n );\n di.registerFactory(() => UserListViewModel(di<UserRepository>()));\n}\n```\n\n## ViewModel Pattern (without BLoC/Riverpod)\n\n```dart\nclass UserListViewModel extends ChangeNotifier {\n UserListViewModel(this._repository);\n\n final UserRepository _repository;\n\n AsyncState<List<User>> _state = const Loading();\n AsyncState<List<User>> get state => _state;\n\n Future<void> load() async {\n _state = const Loading();\n notifyListeners();\n try {\n final users = await _repository.getAll();\n _state = Success(users);\n } on Exception catch (e) {\n _state = Failure(e);\n }\n notifyListeners();\n }\n}\n```\n\n## UseCase Pattern\n\n```dart\nclass GetUserUseCase {\n const GetUserUseCase(this._repository);\n final UserRepository _repository;\n\n Future<User?> call(String id) => _repository.getById(id);\n}\n\nclass CreateUserUseCase {\n const CreateUserUseCase(this._repository, this._idGenerator);\n final UserRepository _repository;\n final IdGenerator _idGenerator; // injected — domain layer must not depend on uuid package directly\n\n Future<void> call(CreateUserInput input) async {\n // Validate, apply business rules, then persist\n final user = User(id: _idGenerator.generate(), name: input.name, email: input.email);\n await _repository.save(user);\n }\n}\n```\n\n## Immutable State with freezed\n\n```dart\n@freezed\nclass UserState with _$UserState {\n const factory UserState({\n @Default([]) List<User> users,\n @Default(false) bool isLoading,\n String? errorMessage,\n }) = _UserState;\n}\n```\n\n## Clean Architecture Layer Boundaries\n\n```\nlib/\n├── domain/ # Pure Dart — no Flutter, no external packages\n│ ├── entities/\n│ ├── repositories/ # Abstract interfaces\n│ └── usecases/\n├── data/ # Implements domain interfaces\n│ ├── datasources/\n│ ├── models/ # DTOs with fromJson/toJson\n│ └── repositories/\n└── presentation/ # Flutter widgets + state management\n ├── pages/\n ├── widgets/\n └── providers/ (or blocs/ or viewmodels/)\n```\n\n- Domain must not import `package:flutter` or any data-layer package\n- Data layer maps DTOs to domain entities at repository boundaries\n- Presentation calls use cases, not repositories directly\n\n## Navigation (GoRouter)\n\n```dart\nfinal router = GoRouter(\n routes: [\n GoRoute(\n path: '/',\n builder: (context, state) => const HomePage(),\n ),\n GoRoute(\n path: '/users/:id',\n builder: (context, state) {\n final id = state.pathParameters['id']!;\n return UserDetailPage(userId: id);\n },\n ),\n ],\n // refreshListenable re-evaluates redirect whenever auth state changes\n refreshListenable: GoRouterRefreshStream(authCubit.stream),\n redirect: (context, state) {\n final isLoggedIn = context.read<AuthCubit>().state is AuthAuthenticated;\n if (!isLoggedIn && !state.matchedLocation.startsWith('/login')) {\n return '/login';\n }\n return null;\n },\n);\n```\n\n## References\n\nSee skill: `flutter-dart-code-review` for the comprehensive review checklist.\nSee skill: `compose-multiplatform-patterns` for Kotlin Multiplatform/Flutter interop patterns.\n\n### dart/security\n\n# Dart/Flutter Security\n\n> This file extends [common/security.md](../common/security.md) with Dart, Flutter, and mobile-specific content.\n\n## Secrets Management\n\n- Never hardcode API keys, tokens, or credentials in Dart source\n- Use `--dart-define` or `--dart-define-from-file` for compile-time config (values are not truly secret — use a backend proxy for server-side secrets)\n- Use `flutter_dotenv` or equivalent, with `.env` files listed in `.gitignore`\n- Store runtime secrets in platform-secure storage: `flutter_secure_storage` (Keychain on iOS, EncryptedSharedPreferences on Android)\n\n```dart\n// BAD\nconst apiKey = 'sk-abc123...';\n\n// GOOD — compile-time config (not secret, just configurable)\nconst apiKey = String.fromEnvironment('API_KEY');\n\n// GOOD — runtime secret from secure storage\nfinal token = await secureStorage.read(key: 'auth_token');\n```\n\n## Network Security\n\n- Enforce HTTPS — no `http://` calls in production\n- Configure Android `network_security_config.xml` to block cleartext traffic\n- Set `NSAppTransportSecurity` in `Info.plist` to disallow arbitrary loads\n- Set request timeouts on all HTTP clients — never leave defaults\n- Consider certificate pinning for high-security endpoints\n\n```dart\n// Dio with timeout and HTTPS enforcement\nfinal dio = Dio(BaseOptions(\n baseUrl: 'https://api.example.com',\n connectTimeout: const Duration(seconds: 10),\n receiveTimeout: const Duration(seconds: 30),\n));\n```\n\n## Input Validation\n\n- Validate and sanitize all user input before sending to API or storage\n- Never pass unsanitized input to SQL queries — use parameterized queries (sqflite, drift)\n- Sanitize deep link URLs before navigation — validate scheme, host, and path parameters\n- Use `Uri.tryParse` and validate before navigating\n\n```dart\n// BAD — SQL injection\nawait db.rawQuery(\"SELECT * FROM users WHERE email = '$userInput'\");\n\n// GOOD — parameterized\nawait db.query('users', where: 'email = ?', whereArgs: [userInput]);\n\n// BAD — unvalidated deep link\nfinal uri = Uri.parse(incomingLink);\ncontext.go(uri.path); // could navigate to any route\n\n// GOOD — validated deep link\nfinal uri = Uri.tryParse(incomingLink);\nif (uri != null && uri.host == 'myapp.com' && _allowedPaths.contains(uri.path)) {\n context.go(uri.path);\n}\n```\n\n## Data Protection\n\n- Store tokens, PII, and credentials only in `flutter_secure_storage`\n- Never write sensitive data to `SharedPreferences` or local files in plaintext\n- Clear auth state on logout: tokens, cached user data, cookies\n- Use biometric authentication (`local_auth`) for sensitive operations\n- Avoid logging sensitive data — no `print(token)` or `debugPrint(password)`\n\n## Android-Specific\n\n- Declare only required permissions in `AndroidManifest.xml`\n- Export Android components (`Activity`, `Service`, `BroadcastReceiver`) only when necessary; add `android:exported=\"false\"` where not needed\n- Review intent filters — exported components with implicit intent filters are accessible by any app\n- Use `FLAG_SECURE` for screens displaying sensitive data (prevents screenshots)\n\n```xml\n<!-- AndroidManifest.xml — restrict exported components -->\n<activity android:name=\".MainActivity\" android:exported=\"true\">\n <!-- Only the launcher activity needs exported=true -->\n</activity>\n<activity android:name=\".SensitiveActivity\" android:exported=\"false\" />\n```\n\n## iOS-Specific\n\n- Declare only required usage descriptions in `Info.plist` (`NSCameraUsageDescription`, etc.)\n- Store secrets in Keychain — `flutter_secure_storage` uses Keychain on iOS\n- Use App Transport Security (ATS) — disallow arbitrary loads\n- Enable data protection entitlement for sensitive files\n\n## WebView Security\n\n- Use `webview_flutter` v4+ (`WebViewController` / `WebViewWidget`) — the legacy `WebView` widget is removed\n- Disable JavaScript unless explicitly required (`JavaScriptMode.disabled`)\n- Validate URLs before loading — never load arbitrary URLs from deep links\n- Never expose Dart callbacks to JavaScript unless absolutely needed and carefully sandboxed\n- Use `NavigationDelegate.onNavigationRequest` to intercept and validate navigation requests\n\n```dart\n// webview_flutter v4+ API (WebViewController + WebViewWidget)\nfinal controller = WebViewController()\n ..setJavaScriptMode(JavaScriptMode.disabled) // disabled unless required\n ..setNavigationDelegate(\n NavigationDelegate(\n onNavigationRequest: (request) {\n final uri = Uri.tryParse(request.url);\n if (uri == null || uri.host != 'trusted.example.com') {\n return NavigationDecision.prevent;\n }\n return NavigationDecision.navigate;\n },\n ),\n );\n\n// In your widget tree:\nWebViewWidget(controller: controller)\n```\n\n## Obfuscation and Build Security\n\n- Enable obfuscation in release builds: `flutter build apk --obfuscate --split-debug-info=./debug-info/`\n- Keep `--split-debug-info` output out of version control (used for crash symbolication only)\n- Ensure ProGuard/R8 rules don't inadvertently expose serialized classes\n- Run `flutter analyze` and address all warnings before release\n\n### dart/testing\n\n# Dart/Flutter Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Dart and Flutter-specific content.\n\n## Test Framework\n\n- **flutter_test** / **dart:test** — built-in test runner\n- **mockito** (with `@GenerateMocks`) or **mocktail** (no codegen) for mocking\n- **bloc_test** for BLoC/Cubit unit tests\n- **fake_async** for controlling time in unit tests\n- **integration_test** for end-to-end device tests\n\n## Test Types\n\n| Type | Tool | Location | When to Write |\n|------|------|----------|---------------|\n| Unit | `dart:test` | `test/unit/` | All domain logic, state managers, repositories |\n| Widget | `flutter_test` | `test/widget/` | All widgets with meaningful behavior |\n| Golden | `flutter_test` | `test/golden/` | Design-critical UI components |\n| Integration | `integration_test` | `integration_test/` | Critical user flows on real device/emulator |\n\n## Unit Tests: State Managers\n\n### BLoC with `bloc_test`\n\n```dart\ngroup('CartBloc', () {\n late CartBloc bloc;\n late MockCartRepository repository;\n\n setUp(() {\n repository = MockCartRepository();\n bloc = CartBloc(repository);\n });\n\n tearDown(() => bloc.close());\n\n blocTest<CartBloc, CartState>(\n 'emits updated items when CartItemAdded',\n build: () => bloc,\n act: (b) => b.add(CartItemAdded(testItem)),\n expect: () => [CartState(items: [testItem])],\n );\n\n blocTest<CartBloc, CartState>(\n 'emits empty cart when CartCleared',\n seed: () => CartState(items: [testItem]),\n build: () => bloc,\n act: (b) => b.add(CartCleared()),\n expect: () => [const CartState()],\n );\n});\n```\n\n### Riverpod with `ProviderContainer`\n\n```dart\ntest('usersProvider loads users from repository', () async {\n final container = ProviderContainer(\n overrides: [userRepositoryProvider.overrideWithValue(FakeUserRepository())],\n );\n addTearDown(container.dispose);\n\n final result = await container.read(usersProvider.future);\n expect(result, isNotEmpty);\n});\n```\n\n## Widget Tests\n\n```dart\ntestWidgets('CartPage shows item count badge', (tester) async {\n await tester.pumpWidget(\n ProviderScope(\n overrides: [\n cartNotifierProvider.overrideWith(() => FakeCartNotifier([testItem])),\n ],\n child: const MaterialApp(home: CartPage()),\n ),\n );\n\n await tester.pump();\n expect(find.text('1'), findsOneWidget);\n expect(find.byType(CartItemTile), findsOneWidget);\n});\n\ntestWidgets('shows empty state when cart is empty', (tester) async {\n await tester.pumpWidget(\n ProviderScope(\n overrides: [cartNotifierProvider.overrideWith(() => FakeCartNotifier([]))],\n child: const MaterialApp(home: CartPage()),\n ),\n );\n\n await tester.pump();\n expect(find.text('Your cart is empty'), findsOneWidget);\n});\n```\n\n## Fakes Over Mocks\n\nPrefer hand-written fakes for complex dependencies:\n\n```dart\nclass FakeUserRepository implements UserRepository {\n final _users = <String, User>{};\n Object? fetchError;\n\n @override\n Future<User?> getById(String id) async {\n if (fetchError != null) throw fetchError!;\n return _users[id];\n }\n\n @override\n Future<List<User>> getAll() async {\n if (fetchError != null) throw fetchError!;\n return _users.values.toList();\n }\n\n @override\n Stream<List<User>> watchAll() => Stream.value(_users.values.toList());\n\n @override\n Future<void> save(User user) async {\n _users[user.id] = user;\n }\n\n @override\n Future<void> delete(String id) async {\n _users.remove(id);\n }\n\n void addUser(User user) => _users[user.id] = user;\n}\n```\n\n## Async Testing\n\n```dart\n// Use fake_async for controlling timers and Futures\ntest('debounce triggers after 300ms', () {\n fakeAsync((async) {\n final debouncer = Debouncer(delay: const Duration(milliseconds: 300));\n var callCount = 0;\n debouncer.run(() => callCount++);\n expect(callCount, 0);\n async.elapse(const Duration(milliseconds: 200));\n expect(callCount, 0);\n async.elapse(const Duration(milliseconds: 200));\n expect(callCount, 1);\n });\n});\n```\n\n## Golden Tests\n\n```dart\ntestWidgets('UserCard golden test', (tester) async {\n await tester.pumpWidget(\n MaterialApp(home: UserCard(user: testUser)),\n );\n\n await expectLater(\n find.byType(UserCard),\n matchesGoldenFile('goldens/user_card.png'),\n );\n});\n```\n\nRun `flutter test --update-goldens` when intentional visual changes are made.\n\n## Test Naming\n\nUse descriptive, behavior-focused names:\n\n```dart\ntest('returns null when user does not exist', () { ... });\ntest('throws NotFoundException when id is empty string', () { ... });\ntestWidgets('disables submit button while form is invalid', (tester) async { ... });\n```\n\n## Test Organization\n\n```\ntest/\n├── unit/\n│ ├── domain/\n│ │ └── usecases/\n│ └── data/\n│ └── repositories/\n├── widget/\n│ └── presentation/\n│ └── pages/\n└── golden/\n └── widgets/\n\nintegration_test/\n└── flows/\n ├── login_flow_test.dart\n └── checkout_flow_test.dart\n```\n\n## Coverage\n\n- Target 80%+ line coverage for business logic (domain + state managers)\n- All state transitions must have tests: loading → success, loading → error, retry\n- Run `flutter test --coverage` and inspect `lcov.info` with a coverage reporter\n- Coverage failures should block CI when below threshold\n\n### fsharp/coding-style\n\n# F# Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with F#-specific content.\n\n## Standards\n\n- Follow standard F# conventions and leverage the type system for correctness\n- Prefer immutability by default; use `mutable` only when justified by performance\n- Keep modules focused and cohesive\n\n## Types and Models\n\n- Prefer discriminated unions for domain modeling over class hierarchies\n- Use records for data with named fields\n- Use single-case unions for type-safe wrappers around primitives\n- Avoid classes unless interop or mutable state requires them\n\n```fsharp\ntype EmailAddress = EmailAddress of string\n\ntype OrderStatus =\n | Pending\n | Confirmed of confirmedAt: DateTimeOffset\n | Shipped of trackingNumber: string\n | Cancelled of reason: string\n\ntype Order =\n { Id: Guid\n CustomerId: string\n Status: OrderStatus\n Items: OrderItem list }\n```\n\n## Immutability\n\n- Records are immutable by default; use `with` expressions for updates\n- Prefer `list`, `map`, `set` over mutable collections\n- Avoid `ref` cells and mutable fields in domain logic\n\n```fsharp\nlet rename (profile: UserProfile) newName =\n { profile with Name = newName }\n```\n\n## Function Style\n\n- Prefer small, composable functions over large methods\n- Use the pipe operator `|>` to build readable data pipelines\n- Prefer pattern matching over if/else chains\n- Use `Option` instead of null; use `Result` for operations that can fail\n\n```fsharp\nlet processOrder order =\n order\n |> validateItems\n |> Result.bind calculateTotal\n |> Result.map applyDiscount\n |> Result.mapError OrderError\n```\n\n## Async and Error Handling\n\n- Use `task { }` for interop with .NET async APIs\n- Use `async { }` for F#-native async workflows\n- Propagate `CancellationToken` through public async APIs\n- Prefer `Result` and railway-oriented programming over exceptions for expected failures\n\n```fsharp\nlet loadOrderAsync (orderId: Guid) (ct: CancellationToken) =\n task {\n let! order = repository.FindAsync(orderId, ct)\n return\n order\n |> Option.defaultWith (fun () ->\n failwith $\"Order {orderId} was not found.\")\n }\n```\n\n## Formatting\n\n- Use `fantomas` for automatic formatting\n- Prefer significant whitespace; avoid unnecessary parentheses\n- Remove unused `open` declarations\n\n### Open Declaration Order\n\nGroup `open` statements into four sections separated by a blank line, each section sorted lexically within itself:\n\n1. `System.*`\n2. `Microsoft.*`\n3. Third-party namespaces\n4. First-party / project namespaces\n\n```fsharp\nopen System\nopen System.Collections.Generic\nopen System.Threading.Tasks\n\nopen Microsoft.AspNetCore.Http\nopen Microsoft.Extensions.Logging\n\nopen FsCheck.Xunit\nopen Swensen.Unquote\n\nopen MyApp.Domain\nopen MyApp.Infrastructure\n```\n\n### fsharp/hooks\n\n# F# Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with F#-specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **fantomas**: Auto-format edited F# files\n- **dotnet build**: Verify the solution or project still compiles after edits\n- **dotnet test --no-build**: Re-run the nearest relevant test project after behavior changes\n\n## Stop Hooks\n\n- Run a final `dotnet build` before ending a session with broad F# changes\n- Warn on modified `appsettings*.json` files so secrets do not get committed\n\n### fsharp/patterns\n\n# F# Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with F#-specific content.\n\n## Result Type for Error Handling\n\nUse `Result<'T, 'TError>` with railway-oriented programming instead of exceptions for expected failures.\n\n```fsharp\ntype OrderError =\n | InvalidCustomer of string\n | EmptyItems\n | ItemOutOfStock of sku: string\n\nlet validateOrder (request: CreateOrderRequest) : Result<ValidatedOrder, OrderError> =\n if String.IsNullOrWhiteSpace request.CustomerId then\n Error(InvalidCustomer \"CustomerId is required\")\n elif request.Items |> List.isEmpty then\n Error EmptyItems\n else\n Ok { CustomerId = request.CustomerId; Items = request.Items }\n```\n\n## Option for Missing Values\n\nPrefer `Option<'T>` over null. Use `Option.map`, `Option.bind`, and `Option.defaultValue` to transform.\n\n```fsharp\nlet findUser (id: Guid) : User option =\n users |> Map.tryFind id\n\nlet getUserEmail userId =\n findUser userId\n |> Option.map (fun u -> u.Email)\n |> Option.defaultValue \"unknown@example.com\"\n```\n\n## Discriminated Unions for Domain Modeling\n\nModel business states explicitly. The compiler enforces exhaustive handling.\n\n```fsharp\ntype PaymentState =\n | AwaitingPayment of amount: decimal\n | Paid of paidAt: DateTimeOffset * transactionId: string\n | Refunded of refundedAt: DateTimeOffset * reason: string\n | Failed of error: string\n\nlet describePayment = function\n | AwaitingPayment amount -> $\"Awaiting payment of {amount:C}\"\n | Paid (at, txn) -> $\"Paid at {at} (txn: {txn})\"\n | Refunded (at, reason) -> $\"Refunded at {at}: {reason}\"\n | Failed error -> $\"Payment failed: {error}\"\n```\n\n## Computation Expressions\n\nUse computation expressions to simplify sequential operations that may fail.\n\n```fsharp\nlet placeOrder request =\n result {\n let! validated = validateOrder request\n let! inventory = checkInventory validated.Items\n let! order = createOrder validated inventory\n return order\n }\n```\n\n## Module Organization\n\n- Group related functions in modules rather than classes\n- Use `[<RequireQualifiedAccess>]` to prevent name collisions\n- Keep modules small and focused on a single responsibility\n\n```fsharp\n[<RequireQualifiedAccess>]\nmodule Order =\n let create customerId items = { Id = Guid.NewGuid(); CustomerId = customerId; Items = items; Status = Pending }\n let confirm order = { order with Status = Confirmed(DateTimeOffset.UtcNow) }\n let cancel reason order = { order with Status = Cancelled reason }\n```\n\n## Dependency Injection\n\n- Define dependencies as function parameters or record-of-functions\n- Use interfaces sparingly, primarily at the boundary with .NET libraries\n- Prefer partial application for injecting dependencies into pipelines\n\n```fsharp\ntype OrderDeps =\n { FindOrder: Guid -> Task<Order option>\n SaveOrder: Order -> Task<unit>\n SendNotification: Order -> Task<unit> }\n\nlet processOrder (deps: OrderDeps) orderId =\n task {\n match! deps.FindOrder orderId with\n | None -> return Error \"Order not found\"\n | Some order ->\n let confirmed = Order.confirm order\n do! deps.SaveOrder confirmed\n do! deps.SendNotification confirmed\n return Ok confirmed\n }\n```\n\n### fsharp/security\n\n# F# Security\n\n> This file extends [common/security.md](../common/security.md) with F#-specific content.\n\n## Secret Management\n\n- Never hardcode API keys, tokens, or connection strings in source code\n- Use environment variables, user secrets for local development, and a secret manager in production\n- Keep `appsettings.*.json` free of real credentials\n\n```fsharp\n// BAD\nlet apiKey = \"sk-live-123\"\n\n// GOOD\nlet apiKey =\n configuration[\"OpenAI:ApiKey\"]\n |> Option.ofObj\n |> Option.defaultWith (fun () -> failwith \"OpenAI:ApiKey is not configured.\")\n```\n\n## SQL Injection Prevention\n\n- Always use parameterized queries with ADO.NET, Dapper, or EF Core\n- Never concatenate user input into SQL strings\n- Validate sort fields and filter operators before using dynamic query composition\n\n```fsharp\nlet findByCustomer (connection: IDbConnection) customerId =\n task {\n let sql = \"SELECT * FROM Orders WHERE CustomerId = @customerId\"\n return! connection.QueryAsync<Order>(sql, {| customerId = customerId |})\n }\n```\n\n## Input Validation\n\n- Validate inputs at the application boundary using types\n- Use single-case discriminated unions for validated values\n- Reject invalid input before it enters domain logic\n\n```fsharp\ntype ValidatedEmail = private ValidatedEmail of string\n\nmodule ValidatedEmail =\n let create (input: string) =\n if System.Text.RegularExpressions.Regex.IsMatch(input, @\"^[^@]+@[^@]+\\.[^@]+$\") then\n Ok(ValidatedEmail input)\n else\n Error \"Invalid email address\"\n\n let value (ValidatedEmail v) = v\n```\n\n## Authentication and Authorization\n\n- Prefer framework auth handlers instead of custom token parsing\n- Enforce authorization policies at endpoint or handler boundaries\n- Never log raw tokens, passwords, or PII\n\n## Error Handling\n\n- Return safe client-facing messages\n- Log detailed exceptions with structured context server-side\n- Do not expose stack traces, SQL text, or filesystem paths in API responses\n\n## References\n\nSee skill: `security-review` for broader application security review checklists.\n\n### fsharp/testing\n\n# F# Testing\n\n> This file extends [common/testing.md](../common/testing.md) with F#-specific content.\n\n## Test Framework\n\n- Prefer **xUnit** with **FsUnit.xUnit** for F#-friendly assertions\n- Use **Unquote** for quotation-based assertions with clear failure messages\n- Use **FsCheck.xUnit** for property-based testing\n- Use **NSubstitute** or function stubs for mocking dependencies\n- Use **Testcontainers** when integration tests need real infrastructure\n\n## Test Organization\n\n- Mirror `src/` structure under `tests/`\n- Separate unit, integration, and end-to-end coverage clearly\n- Name tests by behavior, not implementation details\n\n```fsharp\nopen Xunit\nopen Swensen.Unquote\n\n[<Fact>]\nlet ``PlaceOrder returns success when request is valid`` () =\n let request = { CustomerId = \"cust-123\"; Items = [ validItem ] }\n let result = OrderService.placeOrder request\n test <@ Result.isOk result @>\n\n[<Fact>]\nlet ``PlaceOrder returns error when items are empty`` () =\n let request = { CustomerId = \"cust-123\"; Items = [] }\n let result = OrderService.placeOrder request\n test <@ Result.isError result @>\n```\n\n## Property-Based Testing with FsCheck\n\n```fsharp\nopen FsCheck.Xunit\n\n[<Property>]\nlet ``order total is never negative`` (items: OrderItem list) =\n let total = Order.calculateTotal items\n total >= 0m\n```\n\n## ASP.NET Core Integration Tests\n\n- Use `WebApplicationFactory<TEntryPoint>` for API integration coverage\n- Test auth, validation, and serialization through HTTP, not by bypassing middleware\n\n## Coverage\n\n- Target 80%+ line coverage\n- Focus coverage on domain logic, validation, auth, and failure paths\n- Run `dotnet test` in CI with coverage collection enabled where available\n\n### golang/coding-style\n\n# Go Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Go specific content.\n\n## Formatting\n\n- **gofmt** and **goimports** are mandatory — no style debates\n\n## Design Principles\n\n- Accept interfaces, return structs\n- Keep interfaces small (1-3 methods)\n\n## Error Handling\n\nAlways wrap errors with context:\n\n```go\nif err != nil {\n return fmt.Errorf(\"failed to create user: %w\", err)\n}\n```\n\n## Reference\n\nSee skill: `golang-patterns` for comprehensive Go idioms and patterns.\n\n### golang/hooks\n\n# Go Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Go specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **gofmt/goimports**: Auto-format `.go` files after edit\n- **go vet**: Run static analysis after editing `.go` files\n- **staticcheck**: Run extended static checks on modified packages\n\n### golang/patterns\n\n# Go Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Go specific content.\n\n## Functional Options\n\n```go\ntype Option func(*Server)\n\nfunc WithPort(port int) Option {\n return func(s *Server) { s.port = port }\n}\n\nfunc NewServer(opts ...Option) *Server {\n s := &Server{port: 8080}\n for _, opt := range opts {\n opt(s)\n }\n return s\n}\n```\n\n## Small Interfaces\n\nDefine interfaces where they are used, not where they are implemented.\n\n## Dependency Injection\n\nUse constructor functions to inject dependencies:\n\n```go\nfunc NewUserService(repo UserRepository, logger Logger) *UserService {\n return &UserService{repo: repo, logger: logger}\n}\n```\n\n## Reference\n\nSee skill: `golang-patterns` for comprehensive Go patterns including concurrency, error handling, and package organization.\n\n### golang/security\n\n# Go Security\n\n> This file extends [common/security.md](../common/security.md) with Go specific content.\n\n## Secret Management\n\n```go\napiKey := os.Getenv(\"OPENAI_API_KEY\")\nif apiKey == \"\" {\n log.Fatal(\"OPENAI_API_KEY not configured\")\n}\n```\n\n## Security Scanning\n\n- Use **gosec** for static security analysis:\n ```bash\n gosec ./...\n ```\n\n## Context & Timeouts\n\nAlways use `context.Context` for timeout control:\n\n```go\nctx, cancel := context.WithTimeout(ctx, 5*time.Second)\ndefer cancel()\n```\n\n### golang/testing\n\n# Go Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Go specific content.\n\n## Framework\n\nUse the standard `go test` with **table-driven tests**.\n\n## Race Detection\n\nAlways run with the `-race` flag:\n\n```bash\ngo test -race ./...\n```\n\n## Coverage\n\n```bash\ngo test -cover ./...\n```\n\n## Reference\n\nSee skill: `golang-testing` for detailed Go testing patterns and helpers.\n\n### java/coding-style\n\n# Java Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Java-specific content.\n\n## Formatting\n\n- **google-java-format** or **Checkstyle** (Google or Sun style) for enforcement\n- One public top-level type per file\n- Consistent indent: 2 or 4 spaces (match project standard)\n- Member order: constants, fields, constructors, public methods, protected, private\n\n## Immutability\n\n- Prefer `record` for value types (Java 16+)\n- Mark fields `final` by default — use mutable state only when required\n- Return defensive copies from public APIs: `List.copyOf()`, `Map.copyOf()`, `Set.copyOf()`\n- Copy-on-write: return new instances rather than mutating existing ones\n\n```java\n// GOOD — immutable value type\npublic record OrderSummary(Long id, String customerName, BigDecimal total) {}\n\n// GOOD — final fields, no setters\npublic class Order {\n private final Long id;\n private final List<LineItem> items;\n\n public List<LineItem> getItems() {\n return List.copyOf(items);\n }\n}\n```\n\n## Naming\n\nFollow standard Java conventions:\n- `PascalCase` for classes, interfaces, records, enums\n- `camelCase` for methods, fields, parameters, local variables\n- `SCREAMING_SNAKE_CASE` for `static final` constants\n- Packages: all lowercase, reverse domain (`com.example.app.service`)\n\n## Modern Java Features\n\nUse modern language features where they improve clarity:\n- **Records** for DTOs and value types (Java 16+)\n- **Sealed classes** for closed type hierarchies (Java 17+)\n- **Pattern matching** with `instanceof` — no explicit cast (Java 16+)\n- **Text blocks** for multi-line strings — SQL, JSON templates (Java 15+)\n- **Switch expressions** with arrow syntax (Java 14+)\n- **Pattern matching in switch** — exhaustive sealed type handling (Java 21+)\n\n```java\n// Pattern matching instanceof\nif (shape instanceof Circle c) {\n return Math.PI * c.radius() * c.radius();\n}\n\n// Sealed type hierarchy\npublic sealed interface PaymentMethod permits CreditCard, BankTransfer, Wallet {}\n\n// Switch expression\nString label = switch (status) {\n case ACTIVE -> \"Active\";\n case SUSPENDED -> \"Suspended\";\n case CLOSED -> \"Closed\";\n};\n```\n\n## Optional Usage\n\n- Return `Optional<T>` from finder methods that may have no result\n- Use `map()`, `flatMap()`, `orElseThrow()` — never call `get()` without `isPresent()`\n- Never use `Optional` as a field type or method parameter\n\n```java\n// GOOD\nreturn repository.findById(id)\n .map(ResponseDto::from)\n .orElseThrow(() -> new OrderNotFoundException(id));\n\n// BAD — Optional as parameter\npublic void process(Optional<String> name) {}\n```\n\n## Error Handling\n\n- Prefer unchecked exceptions for domain errors\n- Create domain-specific exceptions extending `RuntimeException`\n- Avoid broad `catch (Exception e)` unless at top-level handlers\n- Include context in exception messages\n\n```java\npublic class OrderNotFoundException extends RuntimeException {\n public OrderNotFoundException(Long id) {\n super(\"Order not found: id=\" + id);\n }\n}\n```\n\n## Streams\n\n- Use streams for transformations; keep pipelines short (3-4 operations max)\n- Prefer method references when readable: `.map(Order::getTotal)`\n- Avoid side effects in stream operations\n- For complex logic, prefer a loop over a convoluted stream pipeline\n\n## References\n\nSee skill: `java-coding-standards` for full coding standards with examples.\nSee skill: `jpa-patterns` for JPA/Hibernate entity design patterns.\n\n### java/hooks\n\n# Java Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Java-specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **google-java-format**: Auto-format `.java` files after edit\n- **checkstyle**: Run style checks after editing Java files\n- **./mvnw compile** or **./gradlew compileJava**: Verify compilation after changes\n\n### java/patterns\n\n# Java Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Java-specific content.\n\n## Repository Pattern\n\nEncapsulate data access behind an interface:\n\n```java\npublic interface OrderRepository {\n Optional<Order> findById(Long id);\n List<Order> findAll();\n Order save(Order order);\n void deleteById(Long id);\n}\n```\n\nConcrete implementations handle storage details (JPA, JDBC, in-memory for tests).\n\n## Service Layer\n\nBusiness logic in service classes; keep controllers and repositories thin:\n\n```java\npublic class OrderService {\n private final OrderRepository orderRepository;\n private final PaymentGateway paymentGateway;\n\n public OrderService(OrderRepository orderRepository, PaymentGateway paymentGateway) {\n this.orderRepository = orderRepository;\n this.paymentGateway = paymentGateway;\n }\n\n public OrderSummary placeOrder(CreateOrderRequest request) {\n var order = Order.from(request);\n paymentGateway.charge(order.total());\n var saved = orderRepository.save(order);\n return OrderSummary.from(saved);\n }\n}\n```\n\n## Constructor Injection\n\nAlways use constructor injection — never field injection:\n\n```java\n// GOOD — constructor injection (testable, immutable)\npublic class NotificationService {\n private final EmailSender emailSender;\n\n public NotificationService(EmailSender emailSender) {\n this.emailSender = emailSender;\n }\n}\n\n// BAD — field injection (untestable without reflection, requires framework magic)\npublic class NotificationService {\n @Inject // or @Autowired\n private EmailSender emailSender;\n}\n```\n\n## DTO Mapping\n\nUse records for DTOs. Map at service/controller boundaries:\n\n```java\npublic record OrderResponse(Long id, String customer, BigDecimal total) {\n public static OrderResponse from(Order order) {\n return new OrderResponse(order.getId(), order.getCustomerName(), order.getTotal());\n }\n}\n```\n\n## Builder Pattern\n\nUse for objects with many optional parameters:\n\n```java\npublic class SearchCriteria {\n private final String query;\n private final int page;\n private final int size;\n private final String sortBy;\n\n private SearchCriteria(Builder builder) {\n this.query = builder.query;\n this.page = builder.page;\n this.size = builder.size;\n this.sortBy = builder.sortBy;\n }\n\n public static class Builder {\n private String query = \"\";\n private int page = 0;\n private int size = 20;\n private String sortBy = \"id\";\n\n public Builder query(String query) { this.query = query; return this; }\n public Builder page(int page) { this.page = page; return this; }\n public Builder size(int size) { this.size = size; return this; }\n public Builder sortBy(String sortBy) { this.sortBy = sortBy; return this; }\n public SearchCriteria build() { return new SearchCriteria(this); }\n }\n}\n```\n\n## Sealed Types for Domain Models\n\n```java\npublic sealed interface PaymentResult permits PaymentSuccess, PaymentFailure {\n record PaymentSuccess(String transactionId, BigDecimal amount) implements PaymentResult {}\n record PaymentFailure(String errorCode, String message) implements PaymentResult {}\n}\n\n// Exhaustive handling (Java 21+)\nString message = switch (result) {\n case PaymentSuccess s -> \"Paid: \" + s.transactionId();\n case PaymentFailure f -> \"Failed: \" + f.errorCode();\n};\n```\n\n## API Response Envelope\n\nConsistent API responses:\n\n```java\npublic record ApiResponse<T>(boolean success, T data, String error) {\n public static <T> ApiResponse<T> ok(T data) {\n return new ApiResponse<>(true, data, null);\n }\n public static <T> ApiResponse<T> error(String message) {\n return new ApiResponse<>(false, null, message);\n }\n}\n```\n\n## References\n\nSee skill: `springboot-patterns` for Spring Boot architecture patterns.\nSee skill: `quarkus-patterns` for Quarkus architecture patterns with REST, Panache, and messaging.\nSee skill: `jpa-patterns` for entity design and query optimization.\n\n### java/security\n\n# Java Security\n\n> This file extends [common/security.md](../common/security.md) with Java-specific content.\n\n## Secrets Management\n\n- Never hardcode API keys, tokens, or credentials in source code\n- Use environment variables: `System.getenv(\"API_KEY\")`\n- Use a secret manager (Vault, AWS Secrets Manager) for production secrets\n- Keep local config files with secrets in `.gitignore`\n\n```java\n// BAD\nprivate static final String API_KEY = \"sk-abc123...\";\n\n// GOOD — environment variable\nString apiKey = System.getenv(\"PAYMENT_API_KEY\");\nObjects.requireNonNull(apiKey, \"PAYMENT_API_KEY must be set\");\n```\n\n## SQL Injection Prevention\n\n- Always use parameterized queries — never concatenate user input into SQL\n- Use `PreparedStatement` or your framework's parameterized query API\n- Validate and sanitize any input used in native queries\n\n```java\n// BAD — SQL injection via string concatenation\nStatement stmt = conn.createStatement();\nString sql = \"SELECT * FROM orders WHERE name = '\" + name + \"'\";\nstmt.executeQuery(sql);\n\n// GOOD — PreparedStatement with parameterized query\nPreparedStatement ps = conn.prepareStatement(\"SELECT * FROM orders WHERE name = ?\");\nps.setString(1, name);\n\n// GOOD — JDBC template\njdbcTemplate.query(\"SELECT * FROM orders WHERE name = ?\", mapper, name);\n```\n\n## Input Validation\n\n- Validate all user input at system boundaries before processing\n- Use Bean Validation (`@NotNull`, `@NotBlank`, `@Size`) on DTOs when using a validation framework\n- Sanitize file paths and user-provided strings before use\n- Reject input that fails validation with clear error messages\n\n```java\n// Validate manually in plain Java\npublic Order createOrder(String customerName, BigDecimal amount) {\n if (customerName == null || customerName.isBlank()) {\n throw new IllegalArgumentException(\"Customer name is required\");\n }\n if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {\n throw new IllegalArgumentException(\"Amount must be positive\");\n }\n return new Order(customerName, amount);\n}\n```\n\n## Authentication and Authorization\n\n- Never implement custom auth crypto — use established libraries\n- Store passwords with bcrypt or Argon2, never MD5/SHA1\n- Enforce authorization checks at service boundaries\n- Clear sensitive data from logs — never log passwords, tokens, or PII\n\n## Dependency Security\n\n- Run `mvn dependency:tree` or `./gradlew dependencies` to audit transitive dependencies\n- Use OWASP Dependency-Check or Snyk to scan for known CVEs\n- Keep dependencies updated — set up Dependabot or Renovate\n\n## Error Messages\n\n- Never expose stack traces, internal paths, or SQL errors in API responses\n- Map exceptions to safe, generic client messages at handler boundaries\n- Log detailed errors server-side; return generic messages to clients\n\n```java\n// Log the detail, return a generic message\ntry {\n return orderService.findById(id);\n} catch (OrderNotFoundException ex) {\n log.warn(\"Order not found: id={}\", id);\n return ApiResponse.error(\"Resource not found\"); // generic, no internals\n} catch (Exception ex) {\n log.error(\"Unexpected error processing order id={}\", id, ex);\n return ApiResponse.error(\"Internal server error\"); // never expose ex.getMessage()\n}\n```\n\n## References\n\nSee skill: `springboot-security` for Spring Security authentication and authorization patterns.\nSee skill: `quarkus-security` for Quarkus security with JWT/OIDC, RBAC, and CDI.\nSee skill: `security-review` for general security checklists.\n\n### java/testing\n\n# Java Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Java-specific content.\n\n## Test Framework\n\n- **JUnit 5** (`@Test`, `@ParameterizedTest`, `@Nested`, `@DisplayName`)\n- **AssertJ** for fluent assertions (`assertThat(result).isEqualTo(expected)`)\n- **Mockito** for mocking dependencies\n- **Testcontainers** for integration tests requiring databases or services\n\n## Test Organization\n\n```\nsrc/test/java/com/example/app/\n service/ # Unit tests for service layer\n controller/ # Web layer / API tests\n repository/ # Data access tests\n integration/ # Cross-layer integration tests\n```\n\nMirror the `src/main/java` package structure in `src/test/java`.\n\n## Unit Test Pattern\n\n```java\n@ExtendWith(MockitoExtension.class)\nclass OrderServiceTest {\n\n @Mock\n private OrderRepository orderRepository;\n\n private OrderService orderService;\n\n @BeforeEach\n void setUp() {\n orderService = new OrderService(orderRepository);\n }\n\n @Test\n @DisplayName(\"findById returns order when exists\")\n void findById_existingOrder_returnsOrder() {\n var order = new Order(1L, \"Alice\", BigDecimal.TEN);\n when(orderRepository.findById(1L)).thenReturn(Optional.of(order));\n\n var result = orderService.findById(1L);\n\n assertThat(result.customerName()).isEqualTo(\"Alice\");\n verify(orderRepository).findById(1L);\n }\n\n @Test\n @DisplayName(\"findById throws when order not found\")\n void findById_missingOrder_throws() {\n when(orderRepository.findById(99L)).thenReturn(Optional.empty());\n\n assertThatThrownBy(() -> orderService.findById(99L))\n .isInstanceOf(OrderNotFoundException.class)\n .hasMessageContaining(\"99\");\n }\n}\n```\n\n## Parameterized Tests\n\n```java\n@ParameterizedTest\n@CsvSource({\n \"100.00, 10, 90.00\",\n \"50.00, 0, 50.00\",\n \"200.00, 25, 150.00\"\n})\n@DisplayName(\"discount applied correctly\")\nvoid applyDiscount(BigDecimal price, int pct, BigDecimal expected) {\n assertThat(PricingUtils.discount(price, pct)).isEqualByComparingTo(expected);\n}\n```\n\n## Integration Tests\n\nUse Testcontainers for real database integration:\n\n```java\n@Testcontainers\nclass OrderRepositoryIT {\n\n @Container\n static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(\"postgres:16\");\n\n private OrderRepository repository;\n\n @BeforeEach\n void setUp() {\n var dataSource = new PGSimpleDataSource();\n dataSource.setUrl(postgres.getJdbcUrl());\n dataSource.setUser(postgres.getUsername());\n dataSource.setPassword(postgres.getPassword());\n repository = new JdbcOrderRepository(dataSource);\n }\n\n @Test\n void save_and_findById() {\n var saved = repository.save(new Order(null, \"Bob\", BigDecimal.ONE));\n var found = repository.findById(saved.getId());\n assertThat(found).isPresent();\n }\n}\n```\n\nFor Spring Boot integration tests, see skill: `springboot-tdd`.\nFor Quarkus integration tests, see skill: `quarkus-tdd`.\n\n## Test Naming\n\nUse descriptive names with `@DisplayName`:\n- `methodName_scenario_expectedBehavior()` for method names\n- `@DisplayName(\"human-readable description\")` for reports\n\n## Coverage\n\n- Target 80%+ line coverage\n- Use JaCoCo for coverage reporting\n- Focus on service and domain logic — skip trivial getters/config classes\n\n## References\n\nSee skill: `springboot-tdd` for Spring Boot TDD patterns with MockMvc and Testcontainers.\nSee skill: `quarkus-tdd` for Quarkus TDD patterns with REST Assured and Dev Services.\nSee skill: `java-coding-standards` for testing expectations.\n\n### kotlin/coding-style\n\n# Kotlin Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Kotlin-specific content.\n\n## Formatting\n\n- **ktlint** or **Detekt** for style enforcement\n- Official Kotlin code style (`kotlin.code.style=official` in `gradle.properties`)\n\n## Immutability\n\n- Prefer `val` over `var` — default to `val` and only use `var` when mutation is required\n- Use `data class` for value types; use immutable collections (`List`, `Map`, `Set`) in public APIs\n- Copy-on-write for state updates: `state.copy(field = newValue)`\n\n## Naming\n\nFollow Kotlin conventions:\n- `camelCase` for functions and properties\n- `PascalCase` for classes, interfaces, objects, and type aliases\n- `SCREAMING_SNAKE_CASE` for constants (`const val` or `@JvmStatic`)\n- Prefix interfaces with behavior, not `I`: `Clickable` not `IClickable`\n\n## Null Safety\n\n- Never use `!!` — prefer `?.`, `?:`, `requireNotNull()`, or `checkNotNull()`\n- Use `?.let {}` for scoped null-safe operations\n- Return nullable types from functions that can legitimately have no result\n\n```kotlin\n// BAD\nval name = user!!.name\n\n// GOOD\nval name = user?.name ?: \"Unknown\"\nval name = requireNotNull(user) { \"User must be set before accessing name\" }.name\n```\n\n## Sealed Types\n\nUse sealed classes/interfaces to model closed state hierarchies:\n\n```kotlin\nsealed interface UiState<out T> {\n data object Loading : UiState<Nothing>\n data class Success<T>(val data: T) : UiState<T>\n data class Error(val message: String) : UiState<Nothing>\n}\n```\n\nAlways use exhaustive `when` with sealed types — no `else` branch.\n\n## Extension Functions\n\nUse extension functions for utility operations, but keep them discoverable:\n- Place in a file named after the receiver type (`StringExt.kt`, `FlowExt.kt`)\n- Keep scope limited — don't add extensions to `Any` or overly generic types\n\n## Scope Functions\n\nUse the right scope function:\n- `let` — null check + transform: `user?.let { greet(it) }`\n- `run` — compute a result using receiver: `service.run { fetch(config) }`\n- `apply` — configure an object: `builder.apply { timeout = 30 }`\n- `also` — side effects: `result.also { log(it) }`\n- Avoid deep nesting of scope functions (max 2 levels)\n\n## Error Handling\n\n- Use `Result<T>` or custom sealed types\n- Use `runCatching {}` for wrapping throwable code\n- Never catch `CancellationException` — always rethrow it\n- Avoid `try-catch` for control flow\n\n```kotlin\n// BAD — using exceptions for control flow\nval user = try { repository.getUser(id) } catch (e: NotFoundException) { null }\n\n// GOOD — nullable return\nval user: User? = repository.findUser(id)\n```\n\n### kotlin/hooks\n\n# Kotlin Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Kotlin-specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **ktfmt/ktlint**: Auto-format `.kt` and `.kts` files after edit\n- **detekt**: Run static analysis after editing Kotlin files\n- **./gradlew build**: Verify compilation after changes\n\n### kotlin/patterns\n\n# Kotlin Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Kotlin and Android/KMP-specific content.\n\n## Dependency Injection\n\nPrefer constructor injection. Use Koin (KMP) or Hilt (Android-only):\n\n```kotlin\n// Koin — declare modules\nval dataModule = module {\n single<ItemRepository> { ItemRepositoryImpl(get(), get()) }\n factory { GetItemsUseCase(get()) }\n viewModelOf(::ItemListViewModel)\n}\n\n// Hilt — annotations\n@HiltViewModel\nclass ItemListViewModel @Inject constructor(\n private val getItems: GetItemsUseCase\n) : ViewModel()\n```\n\n## ViewModel Pattern\n\nSingle state object, event sink, one-way data flow:\n\n```kotlin\ndata class ScreenState(\n val items: List<Item> = emptyList(),\n val isLoading: Boolean = false\n)\n\nclass ScreenViewModel(private val useCase: GetItemsUseCase) : ViewModel() {\n private val _state = MutableStateFlow(ScreenState())\n val state = _state.asStateFlow()\n\n fun onEvent(event: ScreenEvent) {\n when (event) {\n is ScreenEvent.Load -> load()\n is ScreenEvent.Delete -> delete(event.id)\n }\n }\n}\n```\n\n## Repository Pattern\n\n- `suspend` functions return `Result<T>` or custom error type\n- `Flow` for reactive streams\n- Coordinate local + remote data sources\n\n```kotlin\ninterface ItemRepository {\n suspend fun getById(id: String): Result<Item>\n suspend fun getAll(): Result<List<Item>>\n fun observeAll(): Flow<List<Item>>\n}\n```\n\n## UseCase Pattern\n\nSingle responsibility, `operator fun invoke`:\n\n```kotlin\nclass GetItemUseCase(private val repository: ItemRepository) {\n suspend operator fun invoke(id: String): Result<Item> {\n return repository.getById(id)\n }\n}\n\nclass GetItemsUseCase(private val repository: ItemRepository) {\n suspend operator fun invoke(): Result<List<Item>> {\n return repository.getAll()\n }\n}\n```\n\n## expect/actual (KMP)\n\nUse for platform-specific implementations:\n\n```kotlin\n// commonMain\nexpect fun platformName(): String\nexpect class SecureStorage {\n fun save(key: String, value: String)\n fun get(key: String): String?\n}\n\n// androidMain\nactual fun platformName(): String = \"Android\"\nactual class SecureStorage {\n actual fun save(key: String, value: String) { /* EncryptedSharedPreferences */ }\n actual fun get(key: String): String? = null /* ... */\n}\n\n// iosMain\nactual fun platformName(): String = \"iOS\"\nactual class SecureStorage {\n actual fun save(key: String, value: String) { /* Keychain */ }\n actual fun get(key: String): String? = null /* ... */\n}\n```\n\n## Coroutine Patterns\n\n- Use `viewModelScope` in ViewModels, `coroutineScope` for structured child work\n- Use `stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), initialValue)` for StateFlow from cold Flows\n- Use `supervisorScope` when child failures should be independent\n\n## Builder Pattern with DSL\n\n```kotlin\nclass HttpClientConfig {\n var baseUrl: String = \"\"\n var timeout: Long = 30_000\n private val interceptors = mutableListOf<Interceptor>()\n\n fun interceptor(block: () -> Interceptor) {\n interceptors.add(block())\n }\n}\n\nfun httpClient(block: HttpClientConfig.() -> Unit): HttpClient {\n val config = HttpClientConfig().apply(block)\n return HttpClient(config)\n}\n\n// Usage\nval client = httpClient {\n baseUrl = \"https://api.example.com\"\n timeout = 15_000\n interceptor { AuthInterceptor(tokenProvider) }\n}\n```\n\n## References\n\nSee skill: `kotlin-coroutines-flows` for detailed coroutine patterns.\nSee skill: `android-clean-architecture` for module and layer patterns.\n\n### kotlin/security\n\n# Kotlin Security\n\n> This file extends [common/security.md](../common/security.md) with Kotlin and Android/KMP-specific content.\n\n## Secrets Management\n\n- Never hardcode API keys, tokens, or credentials in source code\n- Use `local.properties` (git-ignored) for local development secrets\n- Use `BuildConfig` fields generated from CI secrets for release builds\n- Use `EncryptedSharedPreferences` (Android) or Keychain (iOS) for runtime secret storage\n\n```kotlin\n// BAD\nval apiKey = \"sk-abc123...\"\n\n// GOOD — from BuildConfig (generated at build time)\nval apiKey = BuildConfig.API_KEY\n\n// GOOD — from secure storage at runtime\nval token = secureStorage.get(\"auth_token\")\n```\n\n## Network Security\n\n- Use HTTPS exclusively — configure `network_security_config.xml` to block cleartext\n- Pin certificates for sensitive endpoints using OkHttp `CertificatePinner` or Ktor equivalent\n- Set timeouts on all HTTP clients — never leave defaults (which may be infinite)\n- Validate and sanitize all server responses before use\n\n```xml\n<!-- res/xml/network_security_config.xml -->\n<network-security-config>\n <base-config cleartextTrafficPermitted=\"false\" />\n</network-security-config>\n```\n\n## Input Validation\n\n- Validate all user input before processing or sending to API\n- Use parameterized queries for Room/SQLDelight — never concatenate user input into SQL\n- Sanitize file paths from user input to prevent path traversal\n\n```kotlin\n// BAD — SQL injection\n@Query(\"SELECT * FROM items WHERE name = '$input'\")\n\n// GOOD — parameterized\n@Query(\"SELECT * FROM items WHERE name = :input\")\nfun findByName(input: String): List<ItemEntity>\n```\n\n## Data Protection\n\n- Use `EncryptedSharedPreferences` for sensitive key-value data on Android\n- Use `@Serializable` with explicit field names — don't leak internal property names\n- Clear sensitive data from memory when no longer needed\n- Use `@Keep` or ProGuard rules for serialized classes to prevent name mangling\n\n## Authentication\n\n- Store tokens in secure storage, not in plain SharedPreferences\n- Implement token refresh with proper 401/403 handling\n- Clear all auth state on logout (tokens, cached user data, cookies)\n- Use biometric authentication (`BiometricPrompt`) for sensitive operations\n\n## ProGuard / R8\n\n- Keep rules for all serialized models (`@Serializable`, Gson, Moshi)\n- Keep rules for reflection-based libraries (Koin, Retrofit)\n- Test release builds — obfuscation can break serialization silently\n\n## WebView Security\n\n- Disable JavaScript unless explicitly needed: `settings.javaScriptEnabled = false`\n- Validate URLs before loading in WebView\n- Never expose `@JavascriptInterface` methods that access sensitive data\n- Use `WebViewClient.shouldOverrideUrlLoading()` to control navigation\n\n### kotlin/testing\n\n# Kotlin Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Kotlin and Android/KMP-specific content.\n\n## Test Framework\n\n- **kotlin.test** for multiplatform (KMP) — `@Test`, `assertEquals`, `assertTrue`\n- **JUnit 4/5** for Android-specific tests\n- **Turbine** for testing Flows and StateFlow\n- **kotlinx-coroutines-test** for coroutine testing (`runTest`, `TestDispatcher`)\n\n## ViewModel Testing with Turbine\n\n```kotlin\n@Test\nfun `loading state emitted then data`() = runTest {\n val repo = FakeItemRepository()\n repo.addItem(testItem)\n val viewModel = ItemListViewModel(GetItemsUseCase(repo))\n\n viewModel.state.test {\n assertEquals(ItemListState(), awaitItem()) // initial state\n viewModel.onEvent(ItemListEvent.Load)\n assertTrue(awaitItem().isLoading) // loading\n assertEquals(listOf(testItem), awaitItem().items) // loaded\n }\n}\n```\n\n## Fakes Over Mocks\n\nPrefer hand-written fakes over mocking frameworks:\n\n```kotlin\nclass FakeItemRepository : ItemRepository {\n private val items = mutableListOf<Item>()\n var fetchError: Throwable? = null\n\n override suspend fun getAll(): Result<List<Item>> {\n fetchError?.let { return Result.failure(it) }\n return Result.success(items.toList())\n }\n\n override fun observeAll(): Flow<List<Item>> = flowOf(items.toList())\n\n fun addItem(item: Item) { items.add(item) }\n}\n```\n\n## Coroutine Testing\n\n```kotlin\n@Test\nfun `parallel operations complete`() = runTest {\n val repo = FakeRepository()\n val result = loadDashboard(repo)\n advanceUntilIdle()\n assertNotNull(result.items)\n assertNotNull(result.stats)\n}\n```\n\nUse `runTest` — it auto-advances virtual time and provides `TestScope`.\n\n## Ktor MockEngine\n\n```kotlin\nval mockEngine = MockEngine { request ->\n when (request.url.encodedPath) {\n \"/api/items\" -> respond(\n content = Json.encodeToString(testItems),\n headers = headersOf(HttpHeaders.ContentType, ContentType.Application.Json.toString())\n )\n else -> respondError(HttpStatusCode.NotFound)\n }\n}\n\nval client = HttpClient(mockEngine) {\n install(ContentNegotiation) { json() }\n}\n```\n\n## Room/SQLDelight Testing\n\n- Room: Use `Room.inMemoryDatabaseBuilder()` for in-memory testing\n- SQLDelight: Use `JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)` for JVM tests\n\n```kotlin\n@Test\nfun `insert and query items`() = runTest {\n val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)\n Database.Schema.create(driver)\n val db = Database(driver)\n\n db.itemQueries.insert(\"1\", \"Sample Item\", \"description\")\n val items = db.itemQueries.getAll().executeAsList()\n assertEquals(1, items.size)\n}\n```\n\n## Test Naming\n\nUse backtick-quoted descriptive names:\n\n```kotlin\n@Test\nfun `search with empty query returns all items`() = runTest { }\n\n@Test\nfun `delete item emits updated list without deleted item`() = runTest { }\n```\n\n## Test Organization\n\n```\nsrc/\n├── commonTest/kotlin/ # Shared tests (ViewModel, UseCase, Repository)\n├── androidUnitTest/kotlin/ # Android unit tests (JUnit)\n├── androidInstrumentedTest/kotlin/ # Instrumented tests (Room, UI)\n└── iosTest/kotlin/ # iOS-specific tests\n```\n\nMinimum test coverage: ViewModel + UseCase for every feature.\n\n### nuxt/coding-style\n\n# Nuxt Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Nuxt specific content.\n\n## Directory layout\n\n- Default `srcDir` is `app/`. Framework files live at `app/pages/`, `app/layouts/`, `app/middleware/`, `app/plugins/`, `app/app.config.ts`. `nuxt.config.ts` and `server/` stay at project root.\n- Some projects override `srcDir` to `src/` for a Feature-Sliced Design layout, remapping `dir.pages` (for example to `src/app/routes`), `dir.layouts`, and the `@`/`~` aliases. Always check `nuxt.config.ts` before assuming a path.\n\n## Auto-imports discipline\n\n- Composables in `app/composables/` and `server/utils/` auto-import. Do NOT manually import Nuxt composables (`useFetch`, `useState`, `navigateTo`) or `defineStore` / `storeToRefs`.\n- Do NOT add a standalone `vue-router` dep (Nuxt bundles v5) or hand-mount `createApp` / `createPinia` / `createRouter`. The framework wires these.\n\n## Compiler macros\n\n- `definePageMeta` is a compile-time macro. Static values only, no reactive data and no side-effect calls inside it.\n- Augment typed `PageMeta` via `declare module '#app'` rather than casting.\n\n## Config file separation\n\nThree distinct files, do not conflate.\n\n- `nuxt.config.ts` = build-time only (`routeRules`, `modules`, `nitro`, `ssr` flag). Not reactive.\n- `runtimeConfig` (inside nuxt.config) = per-env runtime values, env-overridable via `NUXT_*`. Root keys are server-only, `public` keys are client-visible.\n- `app/app.config.ts` = public build-fixed reactive settings (theme tokens, feature flags). No env override. NEVER secrets.\n\n## Head and meta\n\n- `app.head` in `nuxt.config.ts` takes static values only.\n- Reactive meta goes through `useHead` / `useSeoMeta` in component setup, never via `app.head`.\n\n## Reference\n\n- ECC skills: `nuxt4-patterns`, `vite-patterns`, `frontend-patterns`.\n- [Nuxt directory structure](https://nuxt.com/docs/guide/directory-structure/app)\n- [Nuxt configuration](https://nuxt.com/docs/api/nuxt-config)\n\n### nuxt/hooks\n\n# Nuxt Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Nuxt specific content.\n\nThese are Claude Code harness hooks for Nuxt work. They run via the harness, not Claude.\n\n## Typecheck\n\n- `nuxi typecheck` wraps `vue-tsc`. Requires `vue-tsc` + `typescript` dev deps.\n- Run on `.vue` / `.ts` edit or pre-commit. Typecheck is project-wide, so debounce it and wrap it in a timeout (mirror `web/hooks.md`, for example `timeout 60 nuxi typecheck`) so a hung type-check is reaped instead of accumulating across fast edits.\n\n## Lint\n\n- Use the `@nuxt/eslint` module (flat-config, project-aware, generates `.nuxt/eslint.config.mjs`).\n- Run `eslint .` or `eslint --fix`. This is the Nuxt-official ESLint integration, prefer it over hand-rolled configs.\n\n## Format\n\n- `prettier --write`, or enable stylistic rules in `@nuxt/eslint` to avoid a Prettier/ESLint conflict.\n- Pick one formatting authority. Do not run both Prettier and ESLint stylistic at once.\n\n## Suggested PostToolUse chain\n\n- On Edit to `app/**` and `server/**`: run `eslint --fix` then `timeout 60 nuxi typecheck`.\n- Order matters: lint-fix first (mutates the file), the timed typecheck second (verifies the result). Debouncing still applies.\n\n## Reference\n\n- ECC skills: `nuxt4-patterns`, `vite-patterns`.\n- [@nuxt/eslint module](https://eslint.nuxt.com/)\n- [nuxi typecheck](https://nuxt.com/docs/api/commands/typecheck)\n\n### nuxt/patterns\n\n# Nuxt Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Nuxt specific content.\n\n## Data-fetch selection\n\nLoad-bearing. Pick by render timing, not habit.\n\n- `useFetch(url)` = SSR-safe, URL-first initial/first-paint data. The default. Forwards the server result through the payload so there is no hydration double-fetch.\n- `useAsyncData(key, fn)` = SSR-safe, custom async logic (SDK / GraphQL / combined calls). The explicit key shares the result across components.\n- `$fetch` = client interactions only (form submit, button click, POST/PUT/DELETE). NOT SSR-safe, double-fetches if used for first paint.\n- Rule: `useFetch` / `useAsyncData` for anything rendered on first paint, `$fetch` only for event-driven mutations.\n\n## Shared state\n\n- `useState('key', () => init)` for SSR-safe shared state. Values must be JSON-serializable.\n- NEVER `export const x = ref()` at module scope. One shared instance leaks across concurrent SSR requests and causes a memory leak.\n- With `@pinia/nuxt`: Pinia for domain state, `useState` for small cross-component primitives.\n- Async server-side init goes in `callOnce(async () => {...})`, not as a side effect inside `useAsyncData`.\n\n## Nitro server routes\n\n- `server/api/*.{get,post}.ts` auto-register by path + method. Handler is `defineEventHandler((event) => ...)`.\n- Errors via `throw createError({ status, statusText })`. Prefer the Web-API `status` / `statusText` over deprecated `statusCode` / `statusMessage`.\n- `server/middleware/` must NOT return a response. Only mutate `event.context` or set headers.\n\n## Route middleware\n\n- `app/middleware/*.ts` with `defineNuxtRouteMiddleware((to, from) => ...)`.\n- Use the `to` / `from` args. Do NOT call `useRoute()` inside middleware.\n- `.global` suffix runs on every route. Return `navigateTo()` to redirect, `abortNavigation()` to stop.\n\n## Hydration-safe rendering\n\n- Route off `status` (`idle | pending | success | error`) for lazy fetches.\n- `useAsyncData` payload uses `devalue` (Date/Map/Set/refs survive). A `server/api` response is `JSON.stringify`-only, so define `toJSON()` for non-JSON types.\n- Shrink payload with `pick` / `transform`. This reduces serialized size, it does not skip the fetch.\n\n## Reference\n\n- ECC skills: `nuxt4-patterns`, `vite-patterns`, `frontend-patterns`.\n- [Nuxt data fetching](https://nuxt.com/docs/getting-started/data-fetching)\n- [Nuxt state management](https://nuxt.com/docs/getting-started/state-management)\n- [Nuxt server engine (Nitro)](https://nuxt.com/docs/guide/directory-structure/server)\n\n### nuxt/security\n\n# Nuxt Security\n\n> This file extends [common/security.md](../common/security.md) with Nuxt specific content.\n\n## runtimeConfig public vs private\n\n- Root `runtimeConfig` keys are server-only. `runtimeConfig.public` serializes into EVERY page payload (client-visible).\n- Secrets go at root only. Never put secrets in `app.config.ts` or `runtimeConfig.public`, both ship to the client bundle.\n- Official warning: \"Be careful not to expose runtime config keys to the client-side by either rendering them or passing them to `useState`.\"\n\n## Server-route input validation\n\n- Use h3 validating readers. Do NOT trust raw `readBody` / `getQuery` / `getRouterParam`.\n - `readValidatedBody(event, schema)` validates the body.\n - `getValidatedQuery(event, schema)` validates the query.\n - `getValidatedRouterParams(event, schema)` validates route params.\n- All accept a validation function or a Zod schema and throw on failure.\n\n## SSR payload leakage\n\n- Anything in `useState`, `useFetch` / `useAsyncData` results, or `runtimeConfig.public` is serialized into the client payload. Never write a secret into those.\n- Use `useServerSeoMeta` for server-only meta with no client cost.\n\n## Cookie and auth passthrough on SSR\n\n- Nuxt does NOT auto-attach the incoming user's cookies to outbound server-side `$fetch`.\n- Forward explicitly with `useRequestFetch()` (cleanest, pre-bound to request headers) or `useRequestHeaders(['cookie'])`.\n- Relay a backend `Set-Cookie` to the browser via `$fetch.raw` + `appendResponseHeader(event, 'set-cookie', ...)`.\n- socket.io is client-only (`.client.ts` plugin), never SSR.\n\n## SSRF on server $fetch\n\n- Server routes run with full network egress. Never pass user-controlled input directly into a server-side `$fetch` URL or host.\n- Validate the param first (h3 utilities above), allowlist the target, pin to `runtimeConfig.public.apiBase`, reject user-supplied absolute URLs.\n- Auto-trigger `/security-review` only for routes that make external network requests (server `$fetch`), handle auth tokens or credentials, or perform sensitive mutations or authorization checks. Examples: SSRF-prone proxy endpoints, token exchange or password reset, admin actions. Skip benign read-only routes that only accept validated query params.\n\n## Reference\n\n- ECC skills: `security-review`, `nuxt4-patterns`.\n- [Nuxt runtime config](https://nuxt.com/docs/guide/going-further/runtime-config)\n- [h3 request utils](https://v1.h3.dev/utils/request)\n\n### nuxt/testing\n\n# Nuxt Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Nuxt specific content.\n\nPackage: `@nuxt/test-utils`. Vitest-first for unit and component tests, with built-in Playwright browser E2E support. nuxt-vitest and vitest-environment-nuxt are superseded and folded into it.\n\n## Setup\n\n- Install dev deps: `@nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core`.\n- Config: `defineVitestConfig({ test: { environment: 'nuxt' } })` from `@nuxt/test-utils/config`. Use `defineVitestProject` for multi-project (separate unit / nuxt / e2e environments).\n- Add `@nuxt/test-utils/module` to `nuxt.config`. Per-file opt-in via `// @vitest-environment nuxt`.\n\n## Runtime helpers\n\nImport from `@nuxt/test-utils/runtime`.\n\n- `mountSuspended(component, opts)` mounts in the Nuxt env with async setup + plugin injection (accepts `@vue/test-utils` mount options + `route`).\n- `renderSuspended(component, opts)` is the Testing Library variant (needs `@testing-library/vue`).\n- `mockNuxtImport(name, factory)` mocks auto-imports (e.g. `useState`). Once per import per file, use `vi.hoisted()`.\n- `mockComponent(name, factory)` mocks by PascalCase name or path.\n- `registerEndpoint(path, handler|opts)` mocks a Nitro endpoint to test server routes or stub the backend. Supports method + `once`.\n\n## E2E helpers\n\nImport from `@nuxt/test-utils/e2e`.\n\n- `await setup({ rootDir, server, browser, ... })` inside the describe block (manages beforeAll/afterAll).\n- Then `$fetch(url)` (rendered HTML), `fetch(url)` (response object), `url(path)` (full URL with port), `createPage(url)` (Playwright).\n- Playwright integration: import `expect` / `test` from `@nuxt/test-utils/playwright`.\n\n## What to test how\n\n- Composables: mock auto-imports with `mockNuxtImport`, mount a host component via `mountSuspended` to exercise `useState` / `useFetch` in the Nuxt runtime.\n- Server routes: `registerEndpoint` to stub, or e2e `$fetch` / `fetch` against the real Nitro server.\n\n## Reference\n\n- ECC skills: `nuxt4-patterns`, `e2e-testing`, `vite-patterns`.\n- [Nuxt testing docs](https://nuxt.com/docs/getting-started/testing)\n- [@nuxt/test-utils npm](https://www.npmjs.com/package/@nuxt/test-utils)\n\n### perl/coding-style\n\n# Perl Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Perl-specific content.\n\n## Standards\n\n- Always `use v5.36` (enables `strict`, `warnings`, `say`, subroutine signatures)\n- Use subroutine signatures — never unpack `@_` manually\n- Prefer `say` over `print` with explicit newlines\n\n## Immutability\n\n- Use **Moo** with `is => 'ro'` and `Types::Standard` for all attributes\n- Never use blessed hashrefs directly — always use Moo/Moose accessors\n- **OO override note**: Moo `has` attributes with `builder` or `default` are acceptable for computed read-only values\n\n## Formatting\n\nUse **perltidy** with these settings:\n\n```\n-i=4 # 4-space indent\n-l=100 # 100 char line length\n-ce # cuddled else\n-bar # opening brace always right\n```\n\n## Linting\n\nUse **perlcritic** at severity 3 with themes: `core`, `pbp`, `security`.\n\n```bash\nperlcritic --severity 3 --theme 'core || pbp || security' lib/\n```\n\n## Reference\n\nSee skill: `perl-patterns` for comprehensive modern Perl idioms and best practices.\n\n### perl/hooks\n\n# Perl Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Perl-specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **perltidy**: Auto-format `.pl` and `.pm` files after edit\n- **perlcritic**: Run lint check after editing `.pm` files\n\n## Warnings\n\n- Warn about `print` in non-script `.pm` files — use `say` or a logging module (e.g., `Log::Any`)\n\n### perl/patterns\n\n# Perl Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Perl-specific content.\n\n## Repository Pattern\n\nUse **DBI** or **DBIx::Class** behind an interface:\n\n```perl\npackage MyApp::Repo::User;\nuse Moo;\n\nhas dbh => (is => 'ro', required => 1);\n\nsub find_by_id ($self, $id) {\n my $sth = $self->dbh->prepare('SELECT * FROM users WHERE id = ?');\n $sth->execute($id);\n return $sth->fetchrow_hashref;\n}\n```\n\n## DTOs / Value Objects\n\nUse **Moo** classes with **Types::Standard** (equivalent to Python dataclasses):\n\n```perl\npackage MyApp::DTO::User;\nuse Moo;\nuse Types::Standard qw(Str Int);\n\nhas name => (is => 'ro', isa => Str, required => 1);\nhas email => (is => 'ro', isa => Str, required => 1);\nhas age => (is => 'ro', isa => Int);\n```\n\n## Resource Management\n\n- Always use **three-arg open** with `autodie`\n- Use **Path::Tiny** for file operations\n\n```perl\nuse autodie;\nuse Path::Tiny;\n\nmy $content = path('config.json')->slurp_utf8;\n```\n\n## Module Interface\n\nUse `Exporter 'import'` with `@EXPORT_OK` — never `@EXPORT`:\n\n```perl\nuse Exporter 'import';\nour @EXPORT_OK = qw(parse_config validate_input);\n```\n\n## Dependency Management\n\nUse **cpanfile** + **carton** for reproducible installs:\n\n```bash\ncarton install\ncarton exec prove -lr t/\n```\n\n## Reference\n\nSee skill: `perl-patterns` for comprehensive modern Perl patterns and idioms.\n\n### perl/security\n\n# Perl Security\n\n> This file extends [common/security.md](../common/security.md) with Perl-specific content.\n\n## Taint Mode\n\n- Use `-T` flag on all CGI/web-facing scripts\n- Sanitize `%ENV` (`$ENV{PATH}`, `$ENV{CDPATH}`, etc.) before any external command\n\n## Input Validation\n\n- Use allowlist regex for untainting — never `/(.*)/s`\n- Validate all user input with explicit patterns:\n\n```perl\nif ($input =~ /\\A([a-zA-Z0-9_-]+)\\z/) {\n my $clean = $1;\n}\n```\n\n## File I/O\n\n- **Three-arg open only** — never two-arg open\n- Prevent path traversal with `Cwd::realpath`:\n\n```perl\nuse Cwd 'realpath';\nmy $safe_path = realpath($user_path);\ndie \"Path traversal\" unless $safe_path =~ m{\\A/allowed/directory/};\n```\n\n## Process Execution\n\n- Use **list-form `system()`** — never single-string form\n- Use **IPC::Run3** for capturing output\n- Never use backticks with variable interpolation\n\n```perl\nsystem('grep', '-r', $pattern, $directory); # safe\n```\n\n## SQL Injection Prevention\n\nAlways use DBI placeholders — never interpolate into SQL:\n\n```perl\nmy $sth = $dbh->prepare('SELECT * FROM users WHERE email = ?');\n$sth->execute($email);\n```\n\n## Security Scanning\n\nRun **perlcritic** with the security theme at severity 4+:\n\n```bash\nperlcritic --severity 4 --theme security lib/\n```\n\n## Reference\n\nSee skill: `perl-security` for comprehensive Perl security patterns, taint mode, and safe I/O.\n\n### perl/testing\n\n# Perl Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Perl-specific content.\n\n## Framework\n\nUse **Test2::V0** for new projects (not Test::More):\n\n```perl\nuse Test2::V0;\n\nis($result, 42, 'answer is correct');\n\ndone_testing;\n```\n\n## Runner\n\n```bash\nprove -l t/ # adds lib/ to @INC\nprove -lr -j8 t/ # recursive, 8 parallel jobs\n```\n\nAlways use `-l` to ensure `lib/` is on `@INC`.\n\n## Coverage\n\nUse **Devel::Cover** — target 80%+:\n\n```bash\ncover -test\n```\n\n## Mocking\n\n- **Test::MockModule** — mock methods on existing modules\n- **Test::MockObject** — create test doubles from scratch\n\n## Pitfalls\n\n- Always end test files with `done_testing`\n- Never forget the `-l` flag with `prove`\n\n## Reference\n\nSee skill: `perl-testing` for detailed Perl TDD patterns with Test2::V0, prove, and Devel::Cover.\n\n### php/coding-style\n\n# PHP Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with PHP specific content.\n\n## Standards\n\n- Follow **PSR-12** formatting and naming conventions.\n- Prefer `declare(strict_types=1);` in application code.\n- Use scalar type hints, return types, and typed properties everywhere new code permits.\n\n## Immutability\n\n- Prefer immutable DTOs and value objects for data crossing service boundaries.\n- Use `readonly` properties or immutable constructors for request/response payloads where possible.\n- Keep arrays for simple maps; promote business-critical structures into explicit classes.\n\n## Formatting\n\n- Use **PHP-CS-Fixer** or **Laravel Pint** for formatting.\n- Use **PHPStan** or **Psalm** for static analysis.\n- Keep Composer scripts checked in so the same commands run locally and in CI.\n\n## Imports\n\n- Add `use` statements for all referenced classes, interfaces, and traits.\n- Avoid relying on the global namespace unless the project explicitly prefers fully qualified names.\n\n## Error Handling\n\n- Throw exceptions for exceptional states; avoid returning `false`/`null` as hidden error channels in new code.\n- Convert framework/request input into validated DTOs before it reaches domain logic.\n\n## Reference\n\nSee skill: `backend-patterns` for broader service/repository layering guidance.\n\n### php/hooks\n\n# PHP Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with PHP specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **Pint / PHP-CS-Fixer**: Auto-format edited `.php` files.\n- **PHPStan / Psalm**: Run static analysis after PHP edits in typed codebases.\n- **PHPUnit / Pest**: Run targeted tests for touched files or modules when edits affect behavior.\n\n## Warnings\n\n- Warn on `var_dump`, `dd`, `dump`, or `die()` left in edited files.\n- Warn when edited PHP files add raw SQL or disable CSRF/session protections.\n\n### php/patterns\n\n# PHP Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with PHP specific content.\n\n## Thin Controllers, Explicit Services\n\n- Keep controllers focused on transport: auth, validation, serialization, status codes.\n- Move business rules into application/domain services that are easy to test without HTTP bootstrapping.\n\n## DTOs and Value Objects\n\n- Replace shape-heavy associative arrays with DTOs for requests, commands, and external API payloads.\n- Use value objects for money, identifiers, date ranges, and other constrained concepts.\n\n## Dependency Injection\n\n- Depend on interfaces or narrow service contracts, not framework globals.\n- Pass collaborators through constructors so services are testable without service-locator lookups.\n\n## Boundaries\n\n- Isolate ORM models from domain decisions when the model layer is doing more than persistence.\n- Wrap third-party SDKs behind small adapters so the rest of the codebase depends on your contract, not theirs.\n\n## Reference\n\nSee skill: `api-design` for endpoint conventions and response-shape guidance.\nSee skill: `laravel-patterns` for Laravel-specific architecture guidance.\n\n### php/security\n\n# PHP Security\n\n> This file extends [common/security.md](../common/security.md) with PHP specific content.\n\n## Input and Output\n\n- Validate request input at the framework boundary (`FormRequest`, Symfony Validator, or explicit DTO validation).\n- Escape output in templates by default; treat raw HTML rendering as an exception that must be justified.\n- Never trust query params, cookies, headers, or uploaded file metadata without validation.\n\n## Database Safety\n\n- Use prepared statements (`PDO`, Doctrine, Eloquent query builder) for all dynamic queries.\n- Avoid string-building SQL in controllers/views.\n- Scope ORM mass-assignment carefully and whitelist writable fields.\n\n## Secrets and Dependencies\n\n- Load secrets from environment variables or a secret manager, never from committed config files.\n- Run `composer audit` in CI and review new package maintainer trust before adding dependencies.\n- Pin major versions deliberately and remove abandoned packages quickly.\n\n## Auth and Session Safety\n\n- Use `password_hash()` / `password_verify()` for password storage.\n- Regenerate session identifiers after authentication and privilege changes.\n- Enforce CSRF protection on state-changing web requests.\n\n## Reference\n\nSee skill: `laravel-security` for Laravel-specific security guidance.\n\n### php/testing\n\n# PHP Testing\n\n> This file extends [common/testing.md](../common/testing.md) with PHP specific content.\n\n## Framework\n\nUse **PHPUnit** as the default test framework. If **Pest** is configured in the project, prefer Pest for new tests and avoid mixing frameworks.\n\n## Coverage\n\n```bash\nvendor/bin/phpunit --coverage-text\n# or\nvendor/bin/pest --coverage\n```\n\nPrefer **pcov** or **Xdebug** in CI, and keep coverage thresholds in CI rather than as tribal knowledge.\n\n## Test Organization\n\n- Separate fast unit tests from framework/database integration tests.\n- Use factory/builders for fixtures instead of large hand-written arrays.\n- Keep HTTP/controller tests focused on transport and validation; move business rules into service-level tests.\n\n## Inertia\n\nIf the project uses Inertia.js, prefer `assertInertia` with `AssertableInertia` to verify component names and props instead of raw JSON assertions.\n\n## Reference\n\nSee skill: `tdd-workflow` for the repo-wide RED -> GREEN -> REFACTOR loop.\nSee skill: `laravel-tdd` for Laravel-specific testing patterns (PHPUnit and Pest).\n\n### python/coding-style\n\n# Python Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Python specific content.\n\n## Standards\n\n- Follow **PEP 8** conventions\n- Use **type annotations** on all function signatures\n\n## Immutability\n\nPrefer immutable data structures:\n\n```python\nfrom dataclasses import dataclass\n\n@dataclass(frozen=True)\nclass User:\n name: str\n email: str\n\nfrom typing import NamedTuple\n\nclass Point(NamedTuple):\n x: float\n y: float\n```\n\n## Formatting\n\n- **black** for code formatting\n- **isort** for import sorting\n- **ruff** for linting\n\n## Reference\n\nSee skill: `python-patterns` for comprehensive Python idioms and patterns.\n\n### python/fastapi\n\n# FastAPI Rules\n\nUse these rules for FastAPI projects alongside the general Python rules.\n\n## Structure\n\n- Put app construction in `create_app()`.\n- Keep routers thin; move persistence and business behavior into services or CRUD helpers.\n- Keep request schemas, update schemas, and response schemas separate.\n- Keep database sessions and auth in dependencies.\n\n## Async\n\n- Use `async def` for endpoints that perform I/O.\n- Use async database and HTTP clients from async endpoints.\n- Do not call `requests`, sync SQLAlchemy sessions, or blocking file/network operations from async routes.\n\n## Dependency Injection\n\n```python\n@router.get(\"/users/{user_id}\")\nasync def get_user(\n user_id: str,\n db: AsyncSession = Depends(get_db),\n current_user: User = Depends(get_current_user),\n):\n ...\n```\n\nDo not create `SessionLocal()` or long-lived clients inside route handlers.\n\n## Schemas\n\n- Never include passwords, password hashes, access tokens, refresh tokens, or internal auth state in response models.\n- Use `response_model` on endpoints that return application data.\n- Use field constraints instead of hand-written validation when Pydantic can express the rule.\n\n## Security\n\n- Keep CORS origins environment-specific.\n- Do not combine wildcard origins with credentialed CORS.\n- Validate JWT expiry, issuer, audience, and algorithm.\n- Rate-limit auth and write-heavy endpoints.\n- Redact credentials, cookies, authorization headers, and tokens from logs.\n\n## Testing\n\n- Override the exact dependency used by `Depends`.\n- Clear `app.dependency_overrides` after tests.\n- Prefer async test clients for async applications.\n\nSee skill: `fastapi-patterns`.\n\n### python/hooks\n\n# Python Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Python specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **black/ruff**: Auto-format `.py` files after edit\n- **mypy/pyright**: Run type checking after editing `.py` files\n\n## Warnings\n\n- Warn about `print()` statements in edited files (use `logging` module instead)\n\n### python/patterns\n\n# Python Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Python specific content.\n\n## Protocol (Duck Typing)\n\n```python\nfrom typing import Protocol\n\nclass Repository(Protocol):\n def find_by_id(self, id: str) -> dict | None: ...\n def save(self, entity: dict) -> dict: ...\n```\n\n## Dataclasses as DTOs\n\n```python\nfrom dataclasses import dataclass\n\n@dataclass\nclass CreateUserRequest:\n name: str\n email: str\n age: int | None = None\n```\n\n## Context Managers & Generators\n\n- Use context managers (`with` statement) for resource management\n- Use generators for lazy evaluation and memory-efficient iteration\n\n## Reference\n\nSee skill: `python-patterns` for comprehensive patterns including decorators, concurrency, and package organization.\n\n### python/security\n\n# Python Security\n\n> This file extends [common/security.md](../common/security.md) with Python specific content.\n\n## Secret Management\n\n```python\nimport os\nfrom dotenv import load_dotenv\n\nload_dotenv()\n\napi_key = os.environ[\"OPENAI_API_KEY\"] # Raises KeyError if missing\n```\n\n## Security Scanning\n\n- Use **bandit** for static security analysis:\n ```bash\n bandit -r src/\n ```\n\n## Reference\n\nSee skill: `django-security` for Django-specific security guidelines (if applicable).\n\n### python/testing\n\n# Python Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Python specific content.\n\n## Framework\n\nUse **pytest** as the testing framework.\n\n## Coverage\n\n```bash\npytest --cov=src --cov-report=term-missing\n```\n\n## Test Organization\n\nUse `pytest.mark` for test categorization:\n\n```python\nimport pytest\n\n@pytest.mark.unit\ndef test_calculate_total():\n ...\n\n@pytest.mark.integration\ndef test_database_connection():\n ...\n```\n\n## Reference\n\nSee skill: `python-testing` for detailed pytest patterns and fixtures.\n\n### react-native/accessibility\n\n# React Native / Expo Accessibility\n\n> Extends the ECC quality bar to accessibility (a11y). Treat a11y as a release requirement, not an afterthought.\n> Target: usable with screen readers (VoiceOver on iOS, TalkBack on Android) and at large font sizes.\n\n## Labeling\n\n- Every interactive element has an `accessibilityRole` and an `accessibilityLabel` (or readable child text).\n- Icon-only buttons MUST have an `accessibilityLabel` — there is no visible text for the reader to announce.\n- Use `accessibilityHint` only when the action is non-obvious; keep it short.\n- Group related elements with `accessible` on the container so they're announced as one unit when appropriate.\n\n```tsx\n<Pressable\n accessibilityRole=\"button\"\n accessibilityLabel=\"Delete item\"\n onPress={onDelete}\n>\n <TrashIcon />\n</Pressable>\n```\n\n## State & Live Regions\n\n- Communicate state with `accessibilityState` (e.g. `{ disabled, selected, checked, expanded }`).\n- Announce async/transient changes (toasts, validation errors) via `accessibilityLiveRegion` (Android) and `AccessibilityInfo.announceForAccessibility` where needed.\n- Reflect loading/error/empty states in text the reader can reach — not just spinners or color.\n\n## Touch Targets & Layout\n\n- Minimum touch target ~44x44pt (iOS) / 48x48dp (Android); use `hitSlop` to enlarge small controls.\n- Respect Dynamic Type / font scaling — avoid fixed heights that clip scaled text; test at the largest accessibility font size.\n- Honor `prefers-reduced-motion` (`AccessibilityInfo.isReduceMotionEnabled`) — gate non-essential animation.\n\n## Color & Contrast\n\n- Do not convey meaning by color alone; pair with text, icon, or shape.\n- Meet WCAG AA contrast: 4.5:1 for body text, 3:1 for large text and meaningful UI/graphical elements.\n- Verify both light and dark themes.\n\n## Focus & Navigation\n\n- Logical focus order; move focus to new content (modals, screens) on open and restore on close.\n- Ensure custom components are reachable and operable by the screen reader, not just by touch.\n\n## Testing\n\n- Manually test with VoiceOver and TalkBack on real devices — automated checks do not catch everything.\n- In component tests, query by role/label (see testing.md) so a11y and tests reinforce each other.\n- Add a11y to the pre-release gate: key flows pass a screen-reader walkthrough.\n\n### react-native/coding-style\n\n# React Native / Expo Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with React Native / Expo specific content.\n\n## Components\n\n- Define props with a named `interface` or `type`; do not use `React.FC`.\n- Keep screens thin: a screen composes hooks + presentational components, it does not hold heavy logic.\n- One component per file for anything reusable; co-locate small private subcomponents.\n- Prefer function components and hooks. No class components.\n\n```tsx\ninterface AvatarProps {\n uri: string\n size?: number\n onPress?: () => void\n}\n\nexport function Avatar({ uri, size = 40, onPress }: AvatarProps) {\n return (\n <Pressable onPress={onPress}>\n <Image source={{ uri }} style={{ width: size, height: size, borderRadius: size / 2 }} />\n </Pressable>\n )\n}\n```\n\n## Styling\n\nPick ONE styling system per project and stay consistent. `StyleSheet.create()` is the framework-native option; utility-class libraries (e.g. NativeWind) are a common alternative. This rule is library-agnostic — what matters is consistency and avoiding inline allocations.\n\n- StyleSheet: define styles with `StyleSheet.create()` at module scope — never build style objects inline inside `render`/JSX on hot paths (it allocates on every render).\n- Utility-class approach: extract repeated class strings into shared constants or a variant helper.\n- Never hardcode raw colors, spacing, or font sizes scattered across files. Centralize design tokens (theme file or config).\n\n```tsx\n// WRONG: inline style object recreated every render\n<View style={{ padding: 16, backgroundColor: '#fff' }} />\n\n// CORRECT (StyleSheet)\nconst styles = StyleSheet.create({ card: { padding: 16, backgroundColor: '#fff' } })\n<View style={styles.card} />\n\n// CORRECT (NativeWind)\n<View className=\"p-4 bg-white\" />\n```\n\n## Platform Differences\n\n- Use platform-specific files (`Component.ios.tsx`, `Component.android.tsx`) for substantial divergence.\n- Use `Platform.select()` / `Platform.OS` for small differences only.\n- Account for safe areas with `react-native-safe-area-context`; do not hardcode status bar / notch offsets.\n\n## Imports & Project Layout\n\n- Use the Expo/TS path alias (e.g. `@/components/...`) instead of long relative chains.\n- Organize by feature/domain, not by type. Keep files focused (200-400 lines typical, 800 max).\n\n## Logging\n\n- No `console.log` in shipped code. Use a logger and strip logs in production builds.\n- Surface user-facing errors through UI state, not console.\n\n## TypeScript\n\nAll TypeScript rules from `rules/typescript/` apply (explicit types on public APIs, avoid `any`, Zod for validation, immutable updates). This file only adds RN-specific guidance on top.\n\n### react-native/hooks\n\n# React Native / Expo Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with React Native / Expo-specific automation guidance.\n\nThese are recommended PostToolUse automations to keep RN/Expo code healthy. Wire them in your hook runtime (or run manually); adapt commands to your package manager.\n\n## Suggested PostToolUse checks (on edit of *.ts/*.tsx)\n\n- **Type check:** `tsc --noEmit` — catch type errors early.\n- **Lint:** `npx expo lint` (uses `eslint-config-expo`; flat config `eslint.config.js` is the default from SDK 53+).\n- **Format:** `prettier --write` on changed files.\n\n## Pre-release / periodic\n\n- `npx expo-doctor` — validates Expo/native dependency health and config.\n- `npx expo install --check` — keeps native deps aligned with the installed Expo SDK.\n- `npm audit` — dependency vulnerability scan.\n\n## Notes\n\n- Do not run heavy native builds inside fast edit hooks; keep edit-time hooks to typecheck/lint/format.\n- Reserve `eas build` / E2E for explicit commands or CI, not per-edit automation.\n- Keep these consistent with ECC hook runtime controls (`ECC_HOOK_PROFILE`, `ECC_DISABLED_HOOKS`).\n\n### react-native/patterns\n\n# React Native / Expo Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with React Native / Expo specific patterns.\n> Note: Do NOT install the `web/` ruleset in a React Native project — those patterns assume the DOM (e.g. URL-as-state) and do not apply here.\n\n## Navigation (Expo Router)\n\nExpo Router is Expo's built-in, file-based router (`app/` directory); React Navigation is the established alternative. The examples below use Expo Router; the principles apply either way.\n\n- Keep route files (`app/**`) thin — they wire params + hooks to a screen component that lives in `components/` or `features/`.\n- Type route params; validate untrusted params (e.g. from deep links) with Zod before use.\n- Use typed navigation helpers (`useLocalSearchParams`, `Link`, `router.push`).\n- Centralize linking config; never trust deep-link params without validation.\n\n```tsx\n// app/user/[id].tsx\nimport { useLocalSearchParams, router } from 'expo-router'\nimport { z } from 'zod'\n\nconst Params = z.object({ id: z.string().uuid() })\n\nexport default function UserScreen() {\n // Use safeParse, not parse: a malformed deep link would otherwise throw\n // during render and crash the screen. Redirect instead of throwing.\n const parsed = Params.safeParse(useLocalSearchParams())\n if (!parsed.success) {\n router.replace('/not-found')\n return null\n }\n return <UserProfile userId={parsed.data.id} />\n}\n```\n\n## State Management\n\nThe rule is to keep these concerns separate and not duplicate server data into client stores. The tools listed are common choices, not requirements — pick what fits your project.\n\n| Concern | Common choices |\n|---------|---------|\n| Server state | a server-cache library (TanStack Query, SWR) |\n| Client/UI state | a lightweight store (Zustand, Jotai) or Context |\n| Navigation/route state | Expo Router params (NOT a global store) |\n| Form state | a form library (e.g. React Hook Form) with schema validation |\n| Secure persistence | `expo-secure-store` |\n| Non-secure persistence | `AsyncStorage` / MMKV |\n\n- Derive values instead of storing redundant computed state.\n- Keep global client state minimal; prefer local `useState` until sharing is actually needed.\n\n## Data Fetching\n\nUse a server-cache library (TanStack Query, SWR) instead of ad-hoc fetch-in-`useEffect`. The examples use TanStack Query.\n\n- Route server reads through the cache (e.g. `useQuery`) and mutations through it (e.g. `useMutation`) with cache invalidation.\n- Validate API responses with Zod at the boundary; infer types from the schema. (Zod is already the validation default in ECC's `typescript/` rules.)\n- Handle the three states explicitly in UI: loading, error, empty.\n- Use optimistic updates for fast interactions: snapshot, apply, roll back on failure with visible feedback.\n- Fetch independent data in parallel; avoid request waterfalls between parent and child.\n\n```tsx\nfunction useUser(id: string) {\n return useQuery({\n queryKey: ['user', id],\n queryFn: async () => userSchema.parse(await api.getUser(id)),\n })\n}\n```\n\n## Lists\n\n- Use `FlatList`/`SectionList` (or `FlashList` for large/heavy lists) — never `.map()` a large array inside a `ScrollView`.\n- Provide a stable `keyExtractor`; memoize `renderItem`.\n- Paginate or virtualize long data sets.\n\n## Custom Hooks\n\n- Extract reusable logic (data, permissions, device APIs) into `use*` hooks.\n- Keep side effects (Expo SDK calls, subscriptions) inside hooks, not in JSX.\n\n## Async & Effects\n\n- Clean up subscriptions, timers, and listeners in the effect's return function.\n- Cancel or ignore stale async results on unmount to avoid setState-after-unmount.\n\n### react-native/performance\n\n# React Native / Expo Performance\n\n> This file extends [common/performance.md](../common/performance.md) with React Native / Expo specific content.\n\n## Rendering\n\n- Memoize expensive components with `React.memo`; memoize callbacks/values passed to children with `useCallback`/`useMemo` only where they prevent real re-renders.\n- Keep component state local and narrow — lifting state too high re-renders large subtrees.\n- Avoid creating new objects/arrays/functions inline in props on hot paths; they break memoization.\n- Split large screens so a state change re-renders the smallest possible subtree.\n\n## Lists\n\n- Use `FlatList`/`SectionList`, or `FlashList` (Shopify) for large or heterogeneous lists.\n- Provide `keyExtractor`, a memoized `renderItem`, and stable item heights when possible (`getItemLayout`).\n- Tune `initialNumToRender`, `windowSize`, `maxToRenderPerBatch` for heavy rows.\n- Never render large data sets with `.map()` inside a `ScrollView`.\n\n## Images & Assets\n\n- Use `expo-image` for caching, priority, and placeholders; serve appropriately sized images.\n- Avoid loading full-resolution images into small thumbnails.\n\n## Animations\n\n- Prefer `react-native-reanimated` (runs on the UI thread) over the JS-driven `Animated` API.\n- For legacy `Animated`, set `useNativeDriver: true` where supported.\n- Keep heavy computation off the JS thread; offload to Reanimated worklets or native modules.\n\n## Runtime & Build\n\n- Build on the **New Architecture** (Fabric + TurboModules). It is the default in recent Expo SDKs (opt-out still available on SDK 53–54) and is mandatory — cannot be disabled — from SDK 55+. Verify every native dependency is New-Arch compatible before shipping.\n- Ensure **Hermes** is enabled (default in modern Expo) for faster startup and lower memory.\n- Defer non-critical work after first paint; lazy-load heavy screens/modules.\n- Use `InteractionManager.runAfterInteractions` for work that can wait until animations finish.\n\n## Measuring\n\n- Profile with the React DevTools profiler, the Hermes sampling profiler, and the in-app performance monitor. (Avoid Flipper — it is deprecated and not supported on the New Architecture.)\n- Watch for: long lists without virtualization, oversized images, frequent full-tree re-renders, and synchronous work on the JS thread.\n\n### react-native/production-readiness\n\n# React Native / Expo Production Readiness\n\n> Extends the ECC philosophy to ship-grade concerns that style/pattern rules cannot encode by themselves.\n> A clean codebase is necessary but not sufficient for production — these items are mandatory before release.\n\n## Architecture\n\n- Ship on the **New Architecture** (Fabric + TurboModules). It is the default in recent Expo SDKs and is mandatory (cannot be disabled) from SDK 55+. Audit native deps for compatibility.\n- Pin the Expo SDK version; upgrade deliberately with `npx expo install --check` and test on both platforms.\n\n## Build & Release (EAS)\n\n- Use **EAS Build** for production binaries and **EAS Submit** for store delivery. Do not rely on local ad-hoc builds for release.\n- Keep separate build profiles (`development`, `preview`, `production`) in `eas.json`.\n- Manage signing credentials via EAS; never commit keystores or provisioning profiles.\n\n## Over-the-Air Updates\n\n- Use **EAS Update** (`expo-updates`) for JS-only fixes, with a defined runtime version policy.\n- Never push native changes via OTA — those require a new store build.\n- Roll out gradually and keep the ability to roll back.\n\n## Observability\n\n- Integrate crash + error reporting (e.g. **Sentry** via `@sentry/react-native`) in production builds.\n- Add structured logging and, where useful, analytics — but strip verbose logs from release.\n- Capture and surface failed network/mutation states; do not fail silently.\n\n## Configuration & Versioning\n\n- Bump `version` and `ios.buildNumber` / `android.versionCode` per release.\n- Public config via `EXPO_PUBLIC_*`; real secrets via EAS secrets only.\n- Validate required config at startup and fail fast with a clear message.\n\n## Pre-Release Gate\n\nBefore shipping, all must pass:\n\n- [ ] `tsc --noEmit` clean\n- [ ] `npx expo lint` clean\n- [ ] Tests green, coverage >= 80% (see testing.md)\n- [ ] `npx expo-doctor` healthy\n- [ ] Critical-flow E2E (Maestro/Detox) pass on a real build\n- [ ] No secrets in bundle (see security.md)\n- [ ] Crash reporting active and verified\n- [ ] Tested on physical iOS and Android devices, not just simulators\n\n### react-native/security\n\n# React Native / Expo Security\n\n> This file extends [common/security.md](../common/security.md) with React Native / Expo specific content.\n> The mandatory pre-commit checklist and Security Response Protocol from common/security.md still apply.\n\n## The Bundle Is Public\n\nTreat everything shipped in the app as readable by an attacker. A mobile binary can be unpacked.\n\n- NEVER ship real secrets (private API keys, service-role keys, signing secrets) in the JS bundle or `app.config`.\n- Public/anon keys (e.g. Supabase anon key, Firebase config) are acceptable ONLY when protected by server-side rules (RLS, security rules). Enforce authorization on the backend, never in the client.\n- Keep privileged operations behind your own server / edge functions.\n\n## Secret & Token Storage\n\n- Store auth tokens and sensitive values in `expo-secure-store` (Keychain / Keystore) — never in `AsyncStorage` or plain MMKV.\n- Do not persist secrets in Redux/Zustand state that may be serialized to disk.\n\n## Configuration\n\n- Read environment via `expo-constants` / `app.config.ts` `extra`, and `EXPO_PUBLIC_*` only for genuinely public values.\n- Keep build secrets in EAS secrets, not in the repo.\n\n## Network & Data\n\n- HTTPS only; reject cleartext. Consider certificate pinning for high-risk apps.\n- Validate ALL external data (API responses, deep-link params, push payloads) with Zod before use.\n- Validate and sanitize deep links and universal links — never route or grant access based on unvalidated params.\n\n## Permissions & Privacy\n\n- Request the minimum device permissions, at the moment they are needed, with clear rationale.\n- Declare data collection accurately for App Store / Play Store privacy disclosures.\n\n## Dependencies\n\n- Run `expo-doctor` and `npm audit` regularly; keep the Expo SDK and native deps current.\n- Use `/security-scan` (AgentShield) on the agent configuration itself.\n\n### react-native/testing\n\n# React Native / Expo Testing\n\n> This file extends [common/testing.md](../common/testing.md) with React Native / Expo specific content.\n> Coverage target and TDD workflow are inherited from common/testing.md (80% minimum, RED-GREEN-REFACTOR).\n\n## Tooling\n\n| Layer | Tool |\n|-------|------|\n| Unit / component | Jest + `@testing-library/react-native` (via `jest-expo` preset) |\n| Hooks | `@testing-library/react-native` `renderHook` |\n| E2E | Maestro (recommended, simple YAML flows) or Detox |\n| Type safety | `tsc --noEmit` in CI |\n\n## Component Tests\n\n- Query by accessible role/label/text, not by `testID` unless necessary — this also enforces accessibility.\n- Assert on user-visible behavior, not implementation details.\n- Follow Arrange-Act-Assert.\n\n```tsx\nimport { render, screen, fireEvent } from '@testing-library/react-native'\n\ntest('calls onSelect with the user id when pressed', () => {\n const onSelect = jest.fn()\n render(<UserCard user={{ id: '1', email: 'a@b.com' }} onSelect={onSelect} />)\n\n fireEvent.press(screen.getByText('a@b.com'))\n\n expect(onSelect).toHaveBeenCalledWith('1')\n})\n```\n\n## Mocking\n\n- Mock Expo SDK modules (camera, location, notifications, secure-store) at the test boundary.\n- Wrap components that use TanStack Query in a `QueryClientProvider` with a fresh client per test.\n- Mock navigation (`expo-router`) so screens render in isolation.\n\n## E2E\n\n- Cover critical flows only: auth, primary navigation, core transactions.\n- Run E2E on CI against a built app (EAS Build) before release.\n\n## What to test first\n\nUse the `tdd-guide` agent proactively for new features: write a failing test that captures the behavior, then implement.\n\n### react/coding-style\n\n# React Coding Style\n\n> This file extends [typescript/coding-style.md](../typescript/coding-style.md) and [common/coding-style.md](../common/coding-style.md) with React specific content.\n\n## File Extensions\n\n- `.tsx` for any file containing JSX, even one-liner snippets\n- `.ts` for pure logic, custom hooks without JSX, type definitions, utilities\n- `.test.tsx` / `.test.ts` mirroring the source file\n- Use `.jsx` only when the project intentionally avoids TypeScript — flag every new untyped React file in review\n\n## Naming\n\n- Components: `PascalCase` for both the symbol and the file (`UserCard.tsx`, default export `UserCard`)\n- Custom hooks: `useCamelCase` for the symbol, kebab-case for the file when the project convention is kebab-case (`use-debounce.ts` exports `useDebounce`)\n- Context: `<Domain>Context` symbol, `<Domain>Provider` provider component, `use<Domain>` consumer hook\n- Event handlers: `handleClick`, `handleSubmit` inside the component; the prop that receives it is `onClick`, `onSubmit`\n- Boolean props: `isLoading`, `hasError`, `canSubmit` — never `loading` or `error` alone for booleans\n\n## Component Shape\n\n```tsx\ntype Props = {\n user: User;\n onSelect: (id: string) => void;\n};\n\nexport function UserCard({ user, onSelect }: Props) {\n return (\n <button type=\"button\" onClick={() => onSelect(user.id)}>\n {user.name}\n </button>\n );\n}\n```\n\n- Prefer `type Props = {}` for closed component prop shapes\n- Use `interface` only when the prop type is extended via declaration merging or exported as a public API extension point\n- Always destructure props in the parameter list — no `props.user` access inside the body\n- Type the return implicitly through JSX (`function Foo(): JSX.Element` only when the function returns conditionally and the union confuses inference)\n\n## JSX\n\n- Self-close tags with no children: `<img />`, `<UserCard user={u} />`\n- Use fragments `<>...</>` over wrapper `<div>` when no DOM element is needed\n- Conditional rendering: `{condition && <Foo />}` for booleans, ternary for either/or, early return for guard clauses\n- Never put logic inline in JSX when it reads as multi-line — extract to a const above the return or a function\n\n```tsx\n// Prefer\nconst greeting = user.isAdmin ? \"Welcome, admin\" : `Hello ${user.name}`;\nreturn <h1>{greeting}</h1>;\n\n// Over\nreturn <h1>{user.isAdmin ? \"Welcome, admin\" : `Hello ${user.name}`}</h1>;\n```\n\n## Server / Client Boundary (Next.js App Router, RSC)\n\n- Default a new file to Server Component — only add `\"use client\"` when the file uses state, effects, refs, browser APIs, or event handlers\n- Place the `\"use client\"` directive on line 1, before any imports\n- Never import a Client Component file from inside a `\"use server\"` action file\n- Never re-export server-only code through a client module — the bundler will silently include it\n\n## Imports\n\n- React imports first: `import { useState } from \"react\"`\n- Then third-party libs, then absolute project imports, then relative\n- Type-only imports: `import type { ReactNode } from \"react\"` — never mix runtime and type imports in one statement when ESLint's `consistent-type-imports` is configured\n\n## Hooks Discipline\n\nSee [hooks.md](./hooks.md) for the full ruleset. Style highlights:\n\n- Custom hooks must start with `use` — enforced by `eslint-plugin-react-hooks`\n- Group all hook calls at the top of the component, before any conditional logic\n- Avoid creating ad-hoc hooks for one-line wrappers — inline the call instead\n\n## State\n\n- Local first (`useState`), lift only when shared\n- Context for cross-cutting state read by many components (theme, auth, i18n) — not for high-frequency updates\n- External store (Zustand, Jotai, Redux Toolkit) when state must persist across route changes, sync across tabs, or be debugged via devtools\n- Never duplicate state that can be derived — compute during render\n\n## Class Components\n\nForbidden in new code. Convert legacy class components to function components when touching them for non-trivial changes.\n\n## File Layout per Component\n\n```\ncomponents/UserCard/\n UserCard.tsx\n UserCard.module.css # or styled-components, or Tailwind classes inline\n UserCard.test.tsx\n index.ts # re-export only\n```\n\nInline single-file components are fine for trivial presentational pieces.\n\n### react/hooks\n\n# React Hooks\n\n> This file covers **React hooks** (`useState`, `useEffect`, `useMemo`, `useCallback`, custom hooks) — NOT the Claude Code `hooks/` runtime system. Naming matches the per-language convention `rules/<lang>/hooks.md` used across this repo.\n>\n> Extends [typescript/patterns.md](../typescript/patterns.md) and [common/patterns.md](../common/patterns.md).\n\n## Rules of Hooks\n\nEnforce `eslint-plugin-react-hooks` with `react-hooks/rules-of-hooks` set to error.\n\n1. Hooks only at the top level of a function component or another hook\n2. Never in loops, conditionals, nested functions, or after early returns\n3. Always called in the same order on every render\n4. Only inside React function components or custom hooks (functions starting with `use`)\n\n```tsx\n// WRONG: conditional hook\nfunction Foo({ enabled }: { enabled: boolean }) {\n if (enabled) {\n const [x, setX] = useState(0); // rule violation\n }\n}\n\n// CORRECT: hook unconditional, condition inside\nfunction Foo({ enabled }: { enabled: boolean }) {\n const [x, setX] = useState(0);\n if (!enabled) return null;\n return <span>{x}</span>;\n}\n```\n\n## `useEffect` — When NOT to Use\n\n`useEffect` is for synchronizing with external systems (subscriptions, browser APIs, third-party libraries). It is **not** the right tool for:\n\n- Derived state — compute it during render\n- Transforming data for rendering — compute it during render\n- Resetting state when a prop changes — use a `key` on the parent or derive from props\n- Notifying parents of state changes — call the callback in the event handler\n- Initializing app-level singletons — call the function module-side or in `main.tsx`\n\n```tsx\n// WRONG: effect for derived state\nconst [fullName, setFullName] = useState(\"\");\nuseEffect(() => {\n setFullName(`${first} ${last}`);\n}, [first, last]);\n\n// CORRECT: derive during render\nconst fullName = `${first} ${last}`;\n```\n\n## Dependency Arrays\n\n- Always include every reactive value referenced inside the effect/callback\n- Enable `react-hooks/exhaustive-deps` lint rule — never silence it without a comment explaining why\n- If the dep array grows unwieldy, the effect is doing too much — split it\n- Stable identity for functions passed in deps: wrap in `useCallback` only when the function is itself a dependency of another hook or passed to a memoized child\n\n## Cleanup\n\nEvery subscription, interval, listener, or in-flight request must clean up.\n\n```tsx\nuseEffect(() => {\n const controller = new AbortController();\n fetch(url, { signal: controller.signal }).then(handleResponse);\n return () => controller.abort();\n}, [url]);\n```\n\n```tsx\nuseEffect(() => {\n const id = setInterval(tick, 1000);\n return () => clearInterval(id);\n}, []);\n```\n\nMissing cleanup = race conditions when deps change, memory leaks on unmount.\n\n## `useMemo` and `useCallback` — When Worth It\n\nDefault position: **do not memoize**. Add `useMemo` / `useCallback` only when:\n\n1. The value is passed to a `React.memo`-wrapped child as a prop, and identity matters\n2. The value is a dependency of another `useEffect` / `useMemo` / `useCallback`\n3. The computation is measurably expensive (profile before assuming)\n\nPremature memoization adds noise, hides bugs, and can be slower than the recompute it replaces.\n\n## Custom Hooks\n\nExtract a custom hook when:\n\n- The same hook sequence (state + effect + computed) appears in 2+ components\n- The logic has a clear, nameable purpose (`useDebounce`, `useOnClickOutside`, `useLocalStorage`)\n- You want to test the logic independently of any component\n\nDo NOT extract when:\n\n- It would have a single caller — inline it\n- The \"hook\" is just `useState` with a different name — adds indirection, no value\n\n```tsx\nexport function useDebounce<T>(value: T, delay: number): T {\n const [debounced, setDebounced] = useState(value);\n useEffect(() => {\n const id = setTimeout(() => setDebounced(value), delay);\n return () => clearTimeout(id);\n }, [value, delay]);\n return debounced;\n}\n```\n\n## `useState` Patterns\n\n- Initial state from prop only at mount: pass a function `useState(() => computeInitial(prop))` when computation is expensive\n- Functional updater when the new state depends on the old: `setCount(c => c + 1)` — never `setCount(count + 1)` inside async or batched contexts\n- Group related state into one object only when they always change together; otherwise split into multiple `useState` calls\n- Use `useReducer` once state transitions are conditional on the previous state or there are 3+ related values\n\n## `useRef` Patterns\n\n- DOM refs for imperative APIs (focus, scroll, third-party libs)\n- Mutable container that does not trigger re-render (timer ids, previous values, \"is mounted\" flags)\n- Never read or write `ref.current` during render — only inside effects or event handlers\n- `useImperativeHandle` only when exposing a child API to a parent ref — last-resort escape hatch\n\n## `useSyncExternalStore`\n\nUse this hook to subscribe to any external store (browser API, third-party state lib, custom event emitter). It is the supported way to make external state safe with concurrent rendering.\n\n```tsx\nconst isOnline = useSyncExternalStore(\n (cb) => {\n window.addEventListener(\"online\", cb);\n window.addEventListener(\"offline\", cb);\n return () => {\n window.removeEventListener(\"online\", cb);\n window.removeEventListener(\"offline\", cb);\n };\n },\n () => navigator.onLine,\n () => true,\n);\n```\n\n## React 19 Additions\n\n- `use()` — unwrap promises and contexts inline; usable conditionally (only hook with that property)\n- `useFormStatus()` / `useFormState()` (or `useActionState`) — form submission state without prop drilling\n- `useOptimistic()` — optimistic UI updates while a server action is pending\n- `useTransition()` — mark non-urgent state updates so urgent ones stay responsive\n\nWhen the project targets React 19+, prefer these over hand-rolled equivalents.\n\n## Stale Closure Trap\n\nAsync handlers and intervals capture the values from the render where they were created. Fix by:\n\n1. Using the functional updater form of `setState`\n2. Putting the changing value in the dep array of `useEffect` and rebuilding the handler\n3. Reading from a ref that is kept in sync\n\n## Lint Configuration\n\nRequired rules:\n\n```json\n{\n \"rules\": {\n \"react-hooks/rules-of-hooks\": \"error\",\n \"react-hooks/exhaustive-deps\": \"warn\"\n }\n}\n```\n\nTreat `exhaustive-deps` warnings as errors in CI for new code.\n\n### react/patterns\n\n# React Patterns\n\n> This file extends [typescript/patterns.md](../typescript/patterns.md) and [common/patterns.md](../common/patterns.md) with React specific content. For hook-specific rules see [hooks.md](./hooks.md).\n\n## Container / Presentational Split\n\nContainer components own data fetching, state, and side effects. Presentational components receive props and render — no service calls, no hooks beyond local UI state.\n\n```tsx\n// Container — owns data\nexport function UserPage({ userId }: { userId: string }) {\n const { data: user, isLoading } = useUser(userId);\n if (isLoading) return <Spinner />;\n if (!user) return <NotFound />;\n return <UserCard user={user} onSelect={handleSelect} />;\n}\n\n// Presentational — pure\nexport function UserCard({ user, onSelect }: { user: User; onSelect: (id: string) => void }) {\n return <button onClick={() => onSelect(user.id)}>{user.name}</button>;\n}\n```\n\n## State Location Decision Tree\n\n1. Used by one component → `useState` inside it\n2. Used by parent + a few children → lift to nearest common ancestor, pass via props\n3. Used across distant branches → React Context **for low-frequency reads only** (theme, auth, locale)\n4. High-frequency updates shared across the tree → external store (Zustand, Jotai, Redux Toolkit)\n5. Server-derived data → server-state library (TanStack Query, SWR, RSC fetch) — not application state\n\nContext misused for frequently changing values causes every consumer to re-render on every update.\n\n## Server / Client Component Boundary (RSC, Next.js App Router)\n\n- Server Components are the default — they run on the server, do not ship to the client, and can `await` directly\n- Client Components opt in with `\"use client\"` at the top of the file\n- Data flows down: a Server Component can render a Client Component and pass serializable props\n- A Client Component cannot import a Server Component, but it can receive one via `children` or named slots\n\n```tsx\n// Server (default)\nexport default async function Page() {\n const user = await fetchUser();\n return <UserClient user={user} />;\n}\n\n// Client\n\"use client\";\nexport function UserClient({ user }: { user: User }) {\n const [tab, setTab] = useState(\"profile\");\n return <Tabs value={tab} onChange={setTab}>{user.name}</Tabs>;\n}\n```\n\n- Never import `\"server-only\"` packages (DB clients, secrets) from a Client Component file — wrap them in a Server Component or Server Action\n- Mark sensitive modules with `import \"server-only\"` so the bundler errors if a client file imports them\n\n## Suspense + Error Boundaries\n\nEvery Suspense boundary needs an Error Boundary above it. The pair handles both states.\n\n```tsx\n<ErrorBoundary fallback={<ErrorView />}>\n <Suspense fallback={<Skeleton />}>\n <UserDetails id={id} />\n </Suspense>\n</ErrorBoundary>\n```\n\n- Place Suspense boundaries close to where data is needed, not at the route root\n- Multiple narrower boundaries reveal loaded content progressively\n- Error Boundary must be a Class Component (React 19 has no functional equivalent yet) OR use a library wrapper such as `react-error-boundary`\n\n## Forms\n\n### Uncontrolled (React 19 + form actions)\n\nPrefer uncontrolled inputs with form actions when the form has a clear submit step. The browser owns the value; React reads it via `FormData` on submit.\n\n```tsx\nasync function action(formData: FormData) {\n \"use server\";\n await saveUser({ name: String(formData.get(\"name\")) });\n}\n\nexport function UserForm() {\n return (\n <form action={action}>\n <input name=\"name\" required />\n <button type=\"submit\">Save</button>\n </form>\n );\n}\n```\n\n### Controlled\n\nUse controlled inputs when the value drives other UI, requires real-time validation, or formatting.\n\n```tsx\nconst [email, setEmail] = useState(\"\");\nreturn <input value={email} onChange={(e) => setEmail(e.target.value)} />;\n```\n\n### Form Libraries\n\nFor complex forms (multi-step, dynamic field arrays, cross-field validation), use a library:\n\n- React Hook Form — minimal re-renders, uncontrolled-first\n- TanStack Form — typed, framework-agnostic\n- Final Form — when subscription-based re-renders matter\n\n## Data Fetching\n\n| Strategy | When |\n|---|---|\n| RSC fetch (`await` in Server Component) | Per-request data in Next.js App Router, no client-side cache needed |\n| TanStack Query | Client-side cache, mutations, optimistic updates, polling |\n| SWR | Lightweight cache + revalidation, simpler than TanStack Query |\n| `fetch` in `useEffect` | Avoid — race conditions, no cache, no retry. Only acceptable for one-off fire-and-forget |\n\nNever fetch in a `useEffect` when a real cache library is available — they handle deduping, cache invalidation, error retry, and Suspense integration.\n\n## Lists and Keys\n\n- `key` must be stable across renders — never `index` for any list that can reorder, insert, or delete\n- `key` must be unique among siblings, not globally\n- A reordered list with index keys causes state in child components to attach to the wrong row\n\n## Composition over Inheritance\n\n- Pass `children` for slot-style composition\n- Pass render-prop functions for parameterized rendering\n- Pass component types for plug-in points: `renderItem={UserRow}`\n- Never extend a component class to specialize behavior\n\n## Compound Components\n\nFor related controls (Tabs, Accordion, Menu), use compound components sharing state via Context:\n\n```tsx\n<Tabs defaultValue=\"profile\">\n <Tabs.List>\n <Tabs.Trigger value=\"profile\">Profile</Tabs.Trigger>\n <Tabs.Trigger value=\"settings\">Settings</Tabs.Trigger>\n </Tabs.List>\n <Tabs.Panel value=\"profile\"><ProfileForm /></Tabs.Panel>\n <Tabs.Panel value=\"settings\"><SettingsForm /></Tabs.Panel>\n</Tabs>\n```\n\n## Portals\n\nUse `createPortal` for modals, tooltips, toast containers — anything that must escape the parent's `overflow: hidden` or `z-index` stacking context. Render to a stable DOM node mounted in `index.html`.\n\n## Refs and Forwarding (React 19+)\n\nReact 19 lets function components accept `ref` as a regular prop — `forwardRef` is no longer required.\n\n```tsx\nexport function Input({ ref, ...rest }: { ref?: React.Ref<HTMLInputElement> } & InputProps) {\n return <input ref={ref} {...rest} />;\n}\n```\n\nOlder codebases on React 18 still need `forwardRef`.\n\n## Out of Scope (Pointer Sections)\n\n### Next.js (App Router)\n\n- Server Actions, Route Handlers, Middleware, Parallel/Intercepted Routes, streaming Metadata\n- Treated as a separate framework concern — when adding deep Next-specific patterns, propose a dedicated `rules/nextjs/` track\n- For now follow Next.js official docs for App Router specifics\n\n### React Native\n\n- Platform-specific imports (`Platform.OS`, `.ios.tsx` / `.android.tsx`), `StyleSheet`, navigation libraries (React Navigation, Expo Router)\n- Treated as a separate track — `rules/react-native/` is not yet present\n- React core hooks/patterns from this file still apply\n\n## Skill Reference\n\nFor React-specific deep dives see `skills/react-patterns/SKILL.md`. For cross-framework frontend concerns see `skills/frontend-patterns/SKILL.md`. For accessibility see `skills/accessibility/SKILL.md`.\n\n### react/security\n\n# React Security\n\n> This file extends [typescript/security.md](../typescript/security.md) and [common/security.md](../common/security.md) with React specific content.\n\n## XSS via `dangerouslySetInnerHTML`\n\nCRITICAL. The prop name is deliberately scary — treat every usage as a code review halt.\n\n```tsx\n// CRITICAL: unsanitized user input\n<div dangerouslySetInnerHTML={{ __html: userBio }} />\n\n// CORRECT options:\n// 1. Render as text\n<div>{userBio}</div>\n\n// 2. Render parsed markdown via a library that sanitizes\n<ReactMarkdown>{userBio}</ReactMarkdown>\n\n// 3. If raw HTML is required, sanitize first with DOMPurify\nimport DOMPurify from \"isomorphic-dompurify\";\n<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userBio) }} />\n```\n\nAudit checklist for every `dangerouslySetInnerHTML` call:\n\n- Is the input always under our control? Document the source.\n- If user-derived: is it sanitized at the **same call site**? (Sanitization at the API boundary is acceptable only if every consumer is verified.)\n- Is the sanitizer config allowlisting tags, not denylisting?\n\n## Unsafe URL Schemes\n\n`javascript:` and `data:` URLs in `href`, `src`, and `xlink:href` execute arbitrary code.\n\n```tsx\n// CRITICAL: javascript: URL injection\n<a href={user.website}>Visit</a> // if user.website = \"javascript:alert(1)\"\n\n// CORRECT: validate scheme\nfunction safeUrl(url: string): string | undefined {\n try {\n const parsed = new URL(url);\n if ([\"http:\", \"https:\", \"mailto:\"].includes(parsed.protocol)) return url;\n } catch {\n return undefined;\n }\n return undefined;\n}\n<a href={safeUrl(user.website)}>Visit</a>\n```\n\nReact warns about `javascript:` URLs in `href` in development mode, but does not block them at runtime. `data:` URLs and other schemes also slip through. Always validate.\n\n## `target=\"_blank\"` Without `rel`\n\n`<a target=\"_blank\">` without `rel=\"noopener noreferrer\"` lets the target page access `window.opener` and run navigation hijacks.\n\n```tsx\n// WRONG\n<a href={externalUrl} target=\"_blank\">External</a>\n\n// CORRECT\n<a href={externalUrl} target=\"_blank\" rel=\"noopener noreferrer\">External</a>\n```\n\nModern browsers default to `noopener` when `target=\"_blank\"`, but do not rely on browser defaults — be explicit.\n\n## Server Action Input Validation\n\nServer Actions (`\"use server\"`) run with the same trust level as a public API endpoint. Validate every input.\n\n```tsx\n\"use server\";\nimport { z } from \"zod\";\n\nconst Input = z.object({\n email: z.string().email(),\n age: z.number().int().min(0).max(120),\n});\n\nexport async function updateUser(_state: unknown, formData: FormData) {\n const parsed = Input.safeParse({\n email: formData.get(\"email\"),\n age: Number(formData.get(\"age\")),\n });\n if (!parsed.success) return { error: parsed.error.flatten() };\n // ...\n}\n```\n\n- Authenticate inside the action — do not trust the client-side route gate\n- Authorize: confirm the current user has permission for the specific record they are mutating\n- Rate limit sensitive actions\n\n## Secret Exposure via Env Vars\n\nPrefixed env vars are bundled into the client. Treat them as public.\n\n| Framework | Public prefix | Private |\n|---|---|---|\n| Next.js | `NEXT_PUBLIC_*` | All others |\n| Vite | `VITE_*` | `.env` server-side only |\n| Create React App | `REACT_APP_*`, plus `NODE_ENV` and `PUBLIC_URL` | All others (anything without the `REACT_APP_` prefix is server-side only) |\n| Remix | `process.env` access in `loader`/`action` only | Same |\n\n```ts\n// CRITICAL: secret leaked to client bundle\nconst apiKey = process.env.NEXT_PUBLIC_STRIPE_SECRET_KEY;\n```\n\nAudit on every PR that touches env vars: would this string in the public bundle be a problem?\n\n## Authentication / Authorization\n\n- Never store sessions in `localStorage` — accessible to any XSS. Use httpOnly secure cookies.\n- Never trust client-set state to gate sensitive UI. Render-gating in JSX prevents display, not access — the API must enforce.\n- CSRF: cookie-based auth requires CSRF tokens or `SameSite=Strict`/`Lax` cookies\n- Use double-submit cookies or origin verification for form actions when not using framework defaults\n\n## Content Security Policy (CSP)\n\nConfigure server-side. The minimum acceptable CSP for a React app:\n\n```\ndefault-src 'self';\nscript-src 'self' 'nonce-{REQUEST_NONCE}';\nstyle-src 'self' 'unsafe-inline';\nimg-src 'self' data: https:;\nconnect-src 'self' https://api.example.com;\nframe-ancestors 'none';\n```\n\n- Avoid `unsafe-inline` and `unsafe-eval` in `script-src`\n- For SSR with inline scripts (Next.js streaming, hydration data), use per-request nonces — both Next.js and Remix support nonce injection\n- `style-src 'unsafe-inline'` is often unavoidable for CSS-in-JS libraries — document the tradeoff\n\n## Prototype Pollution via Object Spread\n\n```tsx\n// WRONG: untrusted JSON spread directly into state\nconst update = await req.json();\nsetState({ ...state, ...update }); // attacker controls __proto__\n\n// CORRECT: parse with a schema, or guard keys\nconst Allowed = z.object({ name: z.string(), email: z.string().email() });\nconst parsed = Allowed.parse(await req.json());\nsetState({ ...state, ...parsed });\n```\n\n## SSR Template Injection\n\nWhen using `renderToString` or `renderToPipeableStream`:\n\n- All values rendered inside JSX are escaped by React — safe\n- Values passed to `dangerouslySetInnerHTML` are NOT escaped — same rules as client\n- Manually constructed HTML wrappers around the React output must be escaped or sanitized — never concatenate user input into the surrounding HTML template\n\n## Third-Party Components\n\n- Audit `npm audit` before adding any UI library\n- Check that the library does not internally use `dangerouslySetInnerHTML` on its input (e.g., rich text editors)\n- Pin versions, review changelogs before major upgrades\n- Be wary of components that accept HTML strings as props\n\n## Source Map Exposure in Production\n\nProduction builds should ship without source maps, or with sourcemaps uploaded to an error tracker (Sentry) and stripped from the public bundle. Public source maps leak internal logic and file structure.\n\n## Agent Support\n\n- Use `security-reviewer` agent for comprehensive security audits across the codebase\n- Use `react-reviewer` agent for React-specific patterns and the above rules in active code review\n\n### react/testing\n\n# React Testing\n\n> This file extends [typescript/testing.md](../typescript/testing.md) and [common/testing.md](../common/testing.md) with React specific content.\n\n## Library Choice\n\n- **React Testing Library (RTL)** — the standard for component testing. Tests behavior through the rendered DOM.\n- **Vitest** — preferred runner for new Vite-based projects. Faster than Jest, native ESM, same API.\n- **Jest** — still the default for Next.js / CRA projects. RTL works identically.\n- **Playwright Component Testing** — when component tests need a real browser engine (animation, layout, complex events)\n- **Cypress Component Testing** — alternative real-browser component runner\n\nPick one component test runner per project — do not mix RTL + Playwright CT in the same repo.\n\n## Core Principle\n\nTest what the user sees and does, not implementation details.\n\n- Query by accessible role first, then label, then text — fall back to `data-testid` only when nothing else fits\n- Never assert on internal state, props passed to children, or which hooks were called\n- Refactor without breaking tests = the test was testing behavior; that is the goal\n\n## Query Priority\n\nRTL exposes queries in three families. Use this priority order top-down:\n\n1. **Accessible to everyone**\n - `getByRole(role, { name })` — primary choice\n - `getByLabelText` — for form inputs\n - `getByPlaceholderText` — when no label is available (and add a label)\n - `getByText` — for non-interactive text\n - `getByDisplayValue` — for form fields with a current value\n\n2. **Semantic queries**\n - `getByAltText` — for images\n - `getByTitle` — last resort, low accessibility value\n\n3. **Test IDs**\n - `getByTestId(\"some-id\")` — escape hatch only, when none of the above work\n\n`getBy*` throws when no match. `queryBy*` returns null (use for asserting absence). `findBy*` returns a promise (use for async).\n\n## User Interaction\n\nPrefer `userEvent` over `fireEvent`. `userEvent` simulates real browser sequences (focus, keydown, beforeinput, input, keyup) — `fireEvent` dispatches a single synthetic event.\n\n```tsx\nimport userEvent from \"@testing-library/user-event\";\n\ntest(\"submits the form\", async () => {\n const user = userEvent.setup();\n render(<UserForm onSubmit={handleSubmit} />);\n\n await user.type(screen.getByLabelText(\"Email\"), \"user@example.com\");\n await user.click(screen.getByRole(\"button\", { name: /save/i }));\n\n expect(handleSubmit).toHaveBeenCalledWith({ email: \"user@example.com\" });\n});\n```\n\n- Always `await` `userEvent` calls — they are async\n- Call `userEvent.setup()` once at the top of each test, then reuse the returned `user`\n\n## Async Assertions\n\n```tsx\n// WRONG: synchronous query for async-rendered content\nexpect(screen.getByText(\"Loaded\")).toBeInTheDocument(); // throws — not in DOM yet\n\n// CORRECT: findBy* (returns a promise, retries)\nexpect(await screen.findByText(\"Loaded\")).toBeInTheDocument();\n\n// CORRECT: waitFor for non-element assertions\nawait waitFor(() => expect(saveSpy).toHaveBeenCalled());\n```\n\n- `findBy*` for async element appearance\n- `waitFor` for async expectations on side effects or other matchers\n- Never `setTimeout` + assertion — flaky\n\n## Network Mocking with MSW\n\nUse Mock Service Worker for any test that hits a network boundary. MSW runs at the network layer, so the component, hooks, and fetch library all behave as in production.\n\n```tsx\n// test setup\nimport { setupServer } from \"msw/node\";\nimport { http, HttpResponse } from \"msw\";\n\nconst server = setupServer(\n http.get(\"/api/users/:id\", ({ params }) =>\n HttpResponse.json({ id: params.id, name: \"Alice\" }),\n ),\n);\n\nbeforeAll(() => server.listen());\nafterEach(() => server.resetHandlers());\nafterAll(() => server.close());\n```\n\nPer-test override:\n\n```tsx\ntest(\"renders error on 500\", async () => {\n server.use(http.get(\"/api/users/:id\", () => new HttpResponse(null, { status: 500 })));\n render(<UserPage id=\"1\" />);\n expect(await screen.findByText(/something went wrong/i)).toBeInTheDocument();\n});\n```\n\n## Avoid Snapshot Tests for Components\n\nSnapshots of rendered output are brittle, hard to review, and rubber-stamped by reviewers. Use them only for:\n\n- Pure data serialization (e.g., a transformer that produces a stable string)\n- Catching unintended regressions in non-visual output\n\nFor component visual regression, use Playwright / Cypress / Percy screenshots — actual visual diffs, not DOM diffs.\n\n## Test Setup Helpers\n\nWrap providers once:\n\n```tsx\nfunction renderWithProviders(ui: React.ReactElement) {\n return render(\n <QueryClientProvider client={new QueryClient()}>\n <ThemeProvider theme={lightTheme}>\n <Router>{ui}</Router>\n </ThemeProvider>\n </QueryClientProvider>,\n );\n}\n```\n\nExport from `test-utils.tsx` and use everywhere.\n\n## Custom Hook Testing\n\nUse `renderHook` from RTL:\n\n```tsx\nimport { renderHook, act } from \"@testing-library/react\";\n\ntest(\"useCounter increments\", () => {\n const { result } = renderHook(() => useCounter());\n act(() => result.current.increment());\n expect(result.current.count).toBe(1);\n});\n```\n\n- Always wrap state-changing calls in `act`\n- Always test through the public hook API, not internal implementation\n\n## Accessibility Assertions\n\n```tsx\nimport { axe } from \"vitest-axe\"; // or jest-axe\n\ntest(\"UserCard has no a11y violations\", async () => {\n const { container } = render(<UserCard user={mockUser} />);\n expect(await axe(container)).toHaveNoViolations();\n});\n```\n\nRun axe assertions in component tests — catches missing labels, ARIA misuse, color contrast (limited).\n\n## When to Reach for Playwright / Cypress\n\nComponent test with RTL + JSDOM cannot:\n\n- Test real layout (flexbox, grid, viewport-dependent rendering)\n- Test scrolling, drag-and-drop, paste from clipboard\n- Test browser-native animation, CSS transitions\n- Test cross-frame interactions (iframes, popups)\n\nFor those, use Playwright Component Testing or end-to-end Playwright/Cypress runs. See [e2e-testing skill](../../skills/e2e-testing/SKILL.md).\n\n## Coverage Targets\n\n| Layer | Target |\n|---|---|\n| Pure utility functions | ≥90% |\n| Custom hooks | ≥85% |\n| Components (presentational) | ≥80% — behavior, not lines |\n| Container components | ≥70% — golden paths + error states |\n| Pages (E2E covered separately) | Smoke test per route minimum |\n\n## Anti-Patterns\n\n- Asserting on `container.querySelector` — bypasses accessibility queries\n- Asserting on number of renders — implementation detail\n- Mocking React hooks (`jest.mock(\"react\", ...)`) — refactor the component instead\n- Mocking child components by default — tests the integration, not the parent in isolation\n- Manual `act()` warnings ignored — they indicate real bugs\n\n## Skill Reference\n\nSee `skills/react-testing/SKILL.md` for end-to-end test examples, MSW patterns, and accessibility test scaffolding.\n\n### ruby/coding-style\n\n# Ruby Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Ruby and Rails specific content.\n\n## Standards\n\n- Target **Ruby 3.3+** for new Rails work unless the project already pins an older supported runtime.\n- Enable **YJIT** in production only after measuring boot time, memory, and request/job throughput.\n- Add `# frozen_string_literal: true` to new Ruby files when the project uses that convention.\n- Prefer clear Ruby over clever metaprogramming; isolate DSL-heavy code behind narrow, tested boundaries.\n\n## Formatting And Linting\n\n- Use the project's checked-in RuboCop config. For Rails 8+ apps, start from `rubocop-rails-omakase` and customize only where the codebase has a real convention.\n- Keep formatter/linter commands behind binstubs or scripts so CI and local runs match:\n\n```bash\nbundle exec rubocop\nbundle exec rubocop -A\n```\n\n- Do not silence cops inline unless the exception is narrow, documented, and harder to express cleanly in code.\n\n## Rails Style\n\n- Follow Rails naming and directory conventions before adding custom structure.\n- Keep controllers transport-focused: authentication, authorization, parameter handling, response shape.\n- Put reusable domain behavior in models, concerns, service objects, query objects, or form objects based on actual complexity, not as default ceremony.\n- Prefer `bin/rails`, `bin/rake`, and checked-in binstubs over globally installed commands.\n\n## Error Handling\n\n- Rescue specific exceptions. Avoid broad `rescue StandardError` blocks unless they re-raise or preserve enough context for operators.\n- Use `ActiveSupport::Notifications` or the app's logger for operational events; do not leave `puts`, `pp`, or `debugger` in committed application code.\n\n## Reference\n\nSee skill: `backend-patterns` for broader service/repository layering guidance.\n\n### ruby/hooks\n\n# Ruby Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Ruby and Rails specific content.\n\n## PostToolUse Hooks\n\nConfigure project-local hooks to prefer binstubs and checked-in tooling:\n\n- **RuboCop**: run `bundle exec rubocop -A <file>` or the project's safer formatter command after Ruby edits.\n- **Brakeman**: run `bundle exec brakeman --no-progress` after security-sensitive Rails changes.\n- **Tests**: run the narrowest matching `bin/rails test ...` or `bundle exec rspec ...` command for touched files.\n- **Bundler audit**: run `bundle exec bundle-audit check --update` when `Gemfile` or `Gemfile.lock` changes and the project has bundler-audit installed.\n\n## Warnings\n\n- Warn on committed `debugger`, `binding.irb`, `binding.pry`, `puts`, `pp`, or `p` calls in application code.\n- Warn when an edit disables CSRF protection, expands mass-assignment, or adds raw SQL without parameterization.\n- Warn when a migration changes data destructively without a reversible path or documented rollout plan.\n\n## CI Gate Suggestions\n\n```bash\nbundle exec rubocop\nbundle exec brakeman --no-progress\nbin/rails test\nbundle exec rspec\n```\n\nUse only the commands that are present in the project; do not install new hook dependencies without maintainer approval.\n\n### ruby/patterns\n\n# Ruby Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Ruby and Rails specific content.\n\n## Rails Way First\n\n- Start with plain Rails MVC and Active Record conventions for small and medium features.\n- Introduce service objects, query objects, form objects, decorators, or presenters when the model/controller boundary is carrying multiple responsibilities.\n- Name extracted objects after the business operation they perform, not after generic layers like `Manager` or `Processor`.\n\n## Persistence\n\n- Prefer PostgreSQL for multi-host production Rails apps unless the existing platform has a clear reason for MySQL or SQLite.\n- Treat Rails 8 SQLite-backed defaults as viable for single-host or modest deployments, not as an automatic fit for shared multi-service systems.\n- Keep raw SQL behind query objects or model scopes and parameterize every dynamic value.\n\n## Background Jobs And Runtime Services\n\n- Use **Solid Queue** for greenfield Rails 8 apps with modest throughput and simple deployment needs.\n- Use **Sidekiq** when the app needs mature observability, high throughput, existing Redis infrastructure, or Pro/Enterprise features.\n- Use **Solid Cache** and **Solid Cable** when their deployment model matches the app; use Redis when shared cross-service behavior, high fanout, or advanced data structures matter.\n\n## Frontend\n\n- Prefer **Hotwire** with Turbo, Stimulus, Importmap, and Propshaft for server-rendered Rails apps.\n- Use React, Vue, Inertia.js, or a separate SPA when interaction complexity, existing product architecture, or team ownership justifies the extra client surface.\n- Keep view components, partials, and presenters focused on rendering decisions; keep persistence and authorization out of templates.\n\n## Authentication\n\n- Use the Rails 8 authentication generator for straightforward session auth and password reset needs.\n- Use Devise or another established auth system when requirements include OAuth, MFA, confirmable/lockable flows, multi-model auth, or a large existing Devise footprint.\n\n## Reference\n\nSee skill: `backend-patterns` for service boundaries and adapter patterns.\n\n### ruby/security\n\n# Ruby Security\n\n> This file extends [common/security.md](../common/security.md) with Ruby and Rails specific content.\n\n## Rails Defaults\n\n- Keep CSRF protection enabled for state-changing browser requests.\n- Use strong parameters or typed boundary objects before mass assignment.\n- Store secrets in Rails credentials, environment variables, or a secret manager. Never commit plaintext keys, tokens, private credentials, or copied `.env` values.\n\n## SQL And Active Record\n\n- Prefer Active Record query APIs and parameterized SQL.\n- Never interpolate request, cookie, header, job, or webhook values into SQL strings.\n- Scope model callbacks carefully; security-sensitive side effects should be explicit and covered by tests.\n\n## Authentication And Sessions\n\n- Use the Rails 8 authentication generator for simple session auth, or Devise when OAuth, MFA, confirmable, lockable, multi-model auth, or existing Devise conventions are required.\n- Rotate sessions after sign-in and privilege changes.\n- Protect account recovery flows with expiry, single-use tokens, rate limiting, and audit logging.\n\n## Dependencies\n\n- Run dependency checks when the lockfile changes:\n\n```bash\nbundle exec bundle-audit check --update\nbundle exec brakeman --no-progress\n```\n\n- Review new gems for maintainer activity, native extension risk, transitive dependencies, and whether the same behavior can be implemented with Rails core.\n\n## Web Safety\n\n- Escape template output by default. Treat `html_safe`, `raw`, and custom sanitizers as security-sensitive code.\n- Validate file uploads by content type, extension, size, and storage destination.\n- Treat background jobs, webhooks, Action Cable messages, and Turbo Stream inputs as untrusted boundaries.\n\n## Reference\n\nSee skill: `security-review` for secure-by-default review patterns.\n\n### ruby/testing\n\n# Ruby Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Ruby and Rails specific content.\n\n## Framework\n\n- Use **Minitest** when the Rails app follows the default Rails test stack.\n- Use **RSpec** when it is already established in the project or the team has explicit production conventions around it.\n- Do not mix Minitest and RSpec inside the same feature area without a migration reason.\n\n## Test Pyramid\n\n- Put fast domain behavior in model, service, query, policy, and job tests.\n- Use request/controller tests for HTTP contracts, auth behavior, redirects, status codes, and response shapes.\n- Use system tests with Capybara for browser-critical flows only; keep them focused and stable.\n- Cover background jobs with unit tests for behavior and integration tests for queue/enqueue contracts.\n\n## Fixtures And Factories\n\n- Use Rails fixtures when they are the project default and the data graph is small.\n- Use `factory_bot` when scenarios need explicit object construction or complex traits.\n- Keep test data close to the behavior being asserted; avoid global fixtures that hide setup cost.\n\n## Commands\n\nPrefer project-local commands:\n\n```bash\nbin/rails test\nbin/rails test test/models/user_test.rb\nbundle exec rspec\nbundle exec rspec spec/models/user_spec.rb\n```\n\n## Coverage\n\n- Use SimpleCov when coverage is enforced; keep thresholds in CI and avoid gaming branch coverage with low-value tests.\n- Add regression tests for bug fixes before changing production code.\n\n## Reference\n\nSee skill: `tdd-workflow` for the repo-wide RED -> GREEN -> REFACTOR loop.\n\n### rust/coding-style\n\n# Rust Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Rust-specific content.\n\n## Formatting\n\n- **rustfmt** for enforcement — always run `cargo fmt` before committing\n- **clippy** for lints — `cargo clippy -- -D warnings` (treat warnings as errors)\n- 4-space indent (rustfmt default)\n- Max line width: 100 characters (rustfmt default)\n\n## Immutability\n\nRust variables are immutable by default — embrace this:\n\n- Use `let` by default; only use `let mut` when mutation is required\n- Prefer returning new values over mutating in place\n- Use `Cow<'_, T>` when a function may or may not need to allocate\n\n```rust\nuse std::borrow::Cow;\n\n// GOOD — immutable by default, new value returned\nfn normalize(input: &str) -> Cow<'_, str> {\n if input.contains(' ') {\n Cow::Owned(input.replace(' ', \"_\"))\n } else {\n Cow::Borrowed(input)\n }\n}\n\n// BAD — unnecessary mutation\nfn normalize_bad(input: &mut String) {\n *input = input.replace(' ', \"_\");\n}\n```\n\n## Naming\n\nFollow standard Rust conventions:\n- `snake_case` for functions, methods, variables, modules, crates\n- `PascalCase` (UpperCamelCase) for types, traits, enums, type parameters\n- `SCREAMING_SNAKE_CASE` for constants and statics\n- Lifetimes: short lowercase (`'a`, `'de`) — descriptive names for complex cases (`'input`)\n\n## Ownership and Borrowing\n\n- Borrow (`&T`) by default; take ownership only when you need to store or consume\n- Never clone to satisfy the borrow checker without understanding the root cause\n- Accept `&str` over `String`, `&[T]` over `Vec<T>` in function parameters\n- Use `impl Into<String>` for constructors that need to own a `String`\n\n```rust\n// GOOD — borrows when ownership isn't needed\nfn word_count(text: &str) -> usize {\n text.split_whitespace().count()\n}\n\n// GOOD — takes ownership in constructor via Into\nfn new(name: impl Into<String>) -> Self {\n Self { name: name.into() }\n}\n\n// BAD — takes String when &str suffices\nfn word_count_bad(text: String) -> usize {\n text.split_whitespace().count()\n}\n```\n\n## Error Handling\n\n- Use `Result<T, E>` and `?` for propagation — never `unwrap()` in production code\n- **Libraries**: define typed errors with `thiserror`\n- **Applications**: use `anyhow` for flexible error context\n- Add context with `.with_context(|| format!(\"failed to ...\"))?`\n- Reserve `unwrap()` / `expect()` for tests and truly unreachable states\n\n```rust\n// GOOD — library error with thiserror\n#[derive(Debug, thiserror::Error)]\npub enum ConfigError {\n #[error(\"failed to read config: {0}\")]\n Io(#[from] std::io::Error),\n #[error(\"invalid config format: {0}\")]\n Parse(String),\n}\n\n// GOOD — application error with anyhow\nuse anyhow::Context;\n\nfn load_config(path: &str) -> anyhow::Result<Config> {\n let content = std::fs::read_to_string(path)\n .with_context(|| format!(\"failed to read {path}\"))?;\n toml::from_str(&content)\n .with_context(|| format!(\"failed to parse {path}\"))\n}\n```\n\n## Iterators Over Loops\n\nPrefer iterator chains for transformations; use loops for complex control flow:\n\n```rust\n// GOOD — declarative and composable\nlet active_emails: Vec<&str> = users.iter()\n .filter(|u| u.is_active)\n .map(|u| u.email.as_str())\n .collect();\n\n// GOOD — loop for complex logic with early returns\nfor user in &users {\n if let Some(verified) = verify_email(&user.email)? {\n send_welcome(&verified)?;\n }\n}\n```\n\n## Module Organization\n\nOrganize by domain, not by type:\n\n```text\nsrc/\n├── main.rs\n├── lib.rs\n├── auth/ # Domain module\n│ ├── mod.rs\n│ ├── token.rs\n│ └── middleware.rs\n├── orders/ # Domain module\n│ ├── mod.rs\n│ ├── model.rs\n│ └── service.rs\n└── db/ # Infrastructure\n ├── mod.rs\n └── pool.rs\n```\n\n## Visibility\n\n- Default to private; use `pub(crate)` for internal sharing\n- Only mark `pub` what is part of the crate's public API\n- Re-export public API from `lib.rs`\n\n## References\n\nSee skill: `rust-patterns` for comprehensive Rust idioms and patterns.\n\n### rust/hooks\n\n# Rust Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Rust-specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **cargo fmt**: Auto-format `.rs` files after edit\n- **cargo clippy**: Run lint checks after editing Rust files\n- **cargo check**: Verify compilation after changes (faster than `cargo build`)\n\n### rust/patterns\n\n# Rust Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Rust-specific content.\n\n## Repository Pattern with Traits\n\nEncapsulate data access behind a trait:\n\n```rust\npub trait OrderRepository: Send + Sync {\n fn find_by_id(&self, id: u64) -> Result<Option<Order>, StorageError>;\n fn find_all(&self) -> Result<Vec<Order>, StorageError>;\n fn save(&self, order: &Order) -> Result<Order, StorageError>;\n fn delete(&self, id: u64) -> Result<(), StorageError>;\n}\n```\n\nConcrete implementations handle storage details (Postgres, SQLite, in-memory for tests).\n\n## Service Layer\n\nBusiness logic in service structs; inject dependencies via constructor:\n\n```rust\npub struct OrderService {\n repo: Box<dyn OrderRepository>,\n payment: Box<dyn PaymentGateway>,\n}\n\nimpl OrderService {\n pub fn new(repo: Box<dyn OrderRepository>, payment: Box<dyn PaymentGateway>) -> Self {\n Self { repo, payment }\n }\n\n pub fn place_order(&self, request: CreateOrderRequest) -> anyhow::Result<OrderSummary> {\n let order = Order::from(request);\n self.payment.charge(order.total())?;\n let saved = self.repo.save(&order)?;\n Ok(OrderSummary::from(saved))\n }\n}\n```\n\n## Newtype Pattern for Type Safety\n\nPrevent argument mix-ups with distinct wrapper types:\n\n```rust\nstruct UserId(u64);\nstruct OrderId(u64);\n\nfn get_order(user: UserId, order: OrderId) -> anyhow::Result<Order> {\n // Can't accidentally swap user and order IDs at call sites\n todo!()\n}\n```\n\n## Enum State Machines\n\nModel states as enums — make illegal states unrepresentable:\n\n```rust\nenum ConnectionState {\n Disconnected,\n Connecting { attempt: u32 },\n Connected { session_id: String },\n Failed { reason: String, retries: u32 },\n}\n\nfn handle(state: &ConnectionState) {\n match state {\n ConnectionState::Disconnected => connect(),\n ConnectionState::Connecting { attempt } if *attempt > 3 => abort(),\n ConnectionState::Connecting { .. } => wait(),\n ConnectionState::Connected { session_id } => use_session(session_id),\n ConnectionState::Failed { retries, .. } if *retries < 5 => retry(),\n ConnectionState::Failed { reason, .. } => log_failure(reason),\n }\n}\n```\n\nAlways match exhaustively — no wildcard `_` for business-critical enums.\n\n## Builder Pattern\n\nUse for structs with many optional parameters:\n\n```rust\npub struct ServerConfig {\n host: String,\n port: u16,\n max_connections: usize,\n}\n\nimpl ServerConfig {\n pub fn builder(host: impl Into<String>, port: u16) -> ServerConfigBuilder {\n ServerConfigBuilder {\n host: host.into(),\n port,\n max_connections: 100,\n }\n }\n}\n\npub struct ServerConfigBuilder {\n host: String,\n port: u16,\n max_connections: usize,\n}\n\nimpl ServerConfigBuilder {\n pub fn max_connections(mut self, n: usize) -> Self {\n self.max_connections = n;\n self\n }\n\n pub fn build(self) -> ServerConfig {\n ServerConfig {\n host: self.host,\n port: self.port,\n max_connections: self.max_connections,\n }\n }\n}\n```\n\n## Sealed Traits for Extensibility Control\n\nUse a private module to seal a trait, preventing external implementations:\n\n```rust\nmod private {\n pub trait Sealed {}\n}\n\npub trait Format: private::Sealed {\n fn encode(&self, data: &[u8]) -> Vec<u8>;\n}\n\npub struct Json;\nimpl private::Sealed for Json {}\nimpl Format for Json {\n fn encode(&self, data: &[u8]) -> Vec<u8> { todo!() }\n}\n```\n\n## API Response Envelope\n\nConsistent API responses using a generic enum:\n\n```rust\n#[derive(Debug, serde::Serialize)]\n#[serde(tag = \"status\")]\npub enum ApiResponse<T: serde::Serialize> {\n #[serde(rename = \"ok\")]\n Ok { data: T },\n #[serde(rename = \"error\")]\n Error { message: String },\n}\n```\n\n## References\n\nSee skill: `rust-patterns` for comprehensive patterns including ownership, traits, generics, concurrency, and async.\n\n### rust/security\n\n# Rust Security\n\n> This file extends [common/security.md](../common/security.md) with Rust-specific content.\n\n## Secrets Management\n\n- Never hardcode API keys, tokens, or credentials in source code\n- Use environment variables: `std::env::var(\"API_KEY\")`\n- Fail fast if required secrets are missing at startup\n- Keep `.env` files in `.gitignore`\n\n```rust\n// BAD\nconst API_KEY: &str = \"sk-abc123...\";\n\n// GOOD — environment variable with early validation\nfn load_api_key() -> anyhow::Result<String> {\n std::env::var(\"PAYMENT_API_KEY\")\n .context(\"PAYMENT_API_KEY must be set\")\n}\n```\n\n## SQL Injection Prevention\n\n- Always use parameterized queries — never format user input into SQL strings\n- Use query builder or ORM (sqlx, diesel, sea-orm) with bind parameters\n\n```rust\n// BAD — SQL injection via format string\nlet query = format!(\"SELECT * FROM users WHERE name = '{name}'\");\nsqlx::query(&query).fetch_one(&pool).await?;\n\n// GOOD — parameterized query with sqlx\n// Placeholder syntax varies by backend: Postgres: $1 | MySQL: ? | SQLite: $1\nsqlx::query(\"SELECT * FROM users WHERE name = $1\")\n .bind(&name)\n .fetch_one(&pool)\n .await?;\n```\n\n## Input Validation\n\n- Validate all user input at system boundaries before processing\n- Use the type system to enforce invariants (newtype pattern)\n- Parse, don't validate — convert unstructured data to typed structs at the boundary\n- Reject invalid input with clear error messages\n\n```rust\n// Parse, don't validate — invalid states are unrepresentable\npub struct Email(String);\n\nimpl Email {\n pub fn parse(input: &str) -> Result<Self, ValidationError> {\n let trimmed = input.trim();\n let at_pos = trimmed.find('@')\n .filter(|&p| p > 0 && p < trimmed.len() - 1)\n .ok_or_else(|| ValidationError::InvalidEmail(input.to_string()))?;\n let domain = &trimmed[at_pos + 1..];\n if trimmed.len() > 254 || !domain.contains('.') {\n return Err(ValidationError::InvalidEmail(input.to_string()));\n }\n // For production use, prefer a validated email crate (e.g., `email_address`)\n Ok(Self(trimmed.to_string()))\n }\n\n pub fn as_str(&self) -> &str {\n &self.0\n }\n}\n```\n\n## Unsafe Code\n\n- Minimize `unsafe` blocks — prefer safe abstractions\n- Every `unsafe` block must have a `// SAFETY:` comment explaining the invariant\n- Never use `unsafe` to bypass the borrow checker for convenience\n- Audit all `unsafe` code during review — it is a red flag without justification\n- Prefer `safe` FFI wrappers around C libraries\n\n```rust\n// GOOD — safety comment documents ALL required invariants\nlet widget: &Widget = {\n // SAFETY: `ptr` is non-null, aligned, points to an initialized Widget,\n // and no mutable references or mutations exist for its lifetime.\n unsafe { &*ptr }\n};\n\n// BAD — no safety justification\nunsafe { &*ptr }\n```\n\n## Dependency Security\n\n- Run `cargo audit` to scan for known CVEs in dependencies\n- Run `cargo deny check` for license and advisory compliance\n- Use `cargo tree` to audit transitive dependencies\n- Keep dependencies updated — set up Dependabot or Renovate\n- Minimize dependency count — evaluate before adding new crates\n\n```bash\n# Security audit\ncargo audit\n\n# Deny advisories, duplicate versions, and restricted licenses\ncargo deny check\n\n# Inspect dependency tree\ncargo tree\ncargo tree -d # Show duplicates only\n```\n\n## Error Messages\n\n- Never expose internal paths, stack traces, or database errors in API responses\n- Log detailed errors server-side; return generic messages to clients\n- Use `tracing` or `log` for structured server-side logging\n\n```rust\n// Map errors to appropriate status codes and generic messages\n// (Example uses axum; adapt the response type to your framework)\nmatch order_service.find_by_id(id) {\n Ok(order) => Ok((StatusCode::OK, Json(order))),\n Err(ServiceError::NotFound(_)) => {\n tracing::info!(order_id = id, \"order not found\");\n Err((StatusCode::NOT_FOUND, \"Resource not found\"))\n }\n Err(e) => {\n tracing::error!(order_id = id, error = %e, \"unexpected error\");\n Err((StatusCode::INTERNAL_SERVER_ERROR, \"Internal server error\"))\n }\n}\n```\n\n## References\n\nSee skill: `rust-patterns` for unsafe code guidelines and ownership patterns.\nSee skill: `security-review` for general security checklists.\n\n### rust/testing\n\n# Rust Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Rust-specific content.\n\n## Test Framework\n\n- **`#[test]`** with `#[cfg(test)]` modules for unit tests\n- **rstest** for parameterized tests and fixtures\n- **proptest** for property-based testing\n- **mockall** for trait-based mocking\n- **`#[tokio::test]`** for async tests\n\n## Test Organization\n\n```text\nmy_crate/\n├── src/\n│ ├── lib.rs # Unit tests in #[cfg(test)] modules\n│ ├── auth/\n│ │ └── mod.rs # #[cfg(test)] mod tests { ... }\n│ └── orders/\n│ └── service.rs # #[cfg(test)] mod tests { ... }\n├── tests/ # Integration tests (each file = separate binary)\n│ ├── api_test.rs\n│ ├── db_test.rs\n│ └── common/ # Shared test utilities\n│ └── mod.rs\n└── benches/ # Criterion benchmarks\n └── benchmark.rs\n```\n\nUnit tests go inside `#[cfg(test)]` modules in the same file. Integration tests go in `tests/`.\n\n## Unit Test Pattern\n\n```rust\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn creates_user_with_valid_email() {\n let user = User::new(\"Alice\", \"alice@example.com\").unwrap();\n assert_eq!(user.name, \"Alice\");\n }\n\n #[test]\n fn rejects_invalid_email() {\n let result = User::new(\"Bob\", \"not-an-email\");\n assert!(result.is_err());\n assert!(result.unwrap_err().to_string().contains(\"invalid email\"));\n }\n}\n```\n\n## Parameterized Tests\n\n```rust\nuse rstest::rstest;\n\n#[rstest]\n#[case(\"hello\", 5)]\n#[case(\"\", 0)]\n#[case(\"rust\", 4)]\nfn test_string_length(#[case] input: &str, #[case] expected: usize) {\n assert_eq!(input.len(), expected);\n}\n```\n\n## Async Tests\n\n```rust\n#[tokio::test]\nasync fn fetches_data_successfully() {\n let client = TestClient::new().await;\n let result = client.get(\"/data\").await;\n assert!(result.is_ok());\n}\n```\n\n## Mocking with mockall\n\nDefine traits in production code; generate mocks in test modules:\n\n```rust\n// Production trait — pub so integration tests can import it\npub trait UserRepository {\n fn find_by_id(&self, id: u64) -> Option<User>;\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n use mockall::predicate::eq;\n\n mockall::mock! {\n pub Repo {}\n impl UserRepository for Repo {\n fn find_by_id(&self, id: u64) -> Option<User>;\n }\n }\n\n #[test]\n fn service_returns_user_when_found() {\n let mut mock = MockRepo::new();\n mock.expect_find_by_id()\n .with(eq(42))\n .times(1)\n .returning(|_| Some(User { id: 42, name: \"Alice\".into() }));\n\n let service = UserService::new(Box::new(mock));\n let user = service.get_user(42).unwrap();\n assert_eq!(user.name, \"Alice\");\n }\n}\n```\n\n## Test Naming\n\nUse descriptive names that explain the scenario:\n- `creates_user_with_valid_email()`\n- `rejects_order_when_insufficient_stock()`\n- `returns_none_when_not_found()`\n\n## Coverage\n\n- Target 80%+ line coverage\n- Use **cargo-llvm-cov** for coverage reporting\n- Focus on business logic — exclude generated code and FFI bindings\n\n```bash\ncargo llvm-cov # Summary\ncargo llvm-cov --html # HTML report\ncargo llvm-cov --fail-under-lines 80 # Fail if below threshold\n```\n\n## Testing Commands\n\n```bash\ncargo test # Run all tests\ncargo test -- --nocapture # Show println output\ncargo test test_name # Run tests matching pattern\ncargo test --lib # Unit tests only\ncargo test --test api_test # Specific integration test (tests/api_test.rs)\ncargo test --doc # Doc tests only\n```\n\n## References\n\nSee skill: `rust-testing` for comprehensive testing patterns including property-based testing, fixtures, and benchmarking with Criterion.\n\n### swift/coding-style\n\n# Swift Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Swift specific content.\n\n## Formatting\n\n- **SwiftFormat** for auto-formatting, **SwiftLint** for style enforcement\n- `swift-format` is bundled with Xcode 16+ as an alternative\n\n## Immutability\n\n- Prefer `let` over `var` — define everything as `let` and only change to `var` if the compiler requires it\n- Use `struct` with value semantics by default; use `class` only when identity or reference semantics are needed\n\n## Naming\n\nFollow [Apple API Design Guidelines](https://www.swift.org/documentation/api-design-guidelines/):\n\n- Clarity at the point of use — omit needless words\n- Name methods and properties for their roles, not their types\n- Use `static let` for constants over global constants\n\n## Error Handling\n\nUse typed throws (Swift 6+) and pattern matching:\n\n```swift\nfunc load(id: String) throws(LoadError) -> Item {\n guard let data = try? read(from: path) else {\n throw .fileNotFound(id)\n }\n return try decode(data)\n}\n```\n\n## Concurrency\n\nEnable Swift 6 strict concurrency checking. Prefer:\n\n- `Sendable` value types for data crossing isolation boundaries\n- Actors for shared mutable state\n- Structured concurrency (`async let`, `TaskGroup`) over unstructured `Task {}`\n\n### swift/hooks\n\n# Swift Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Swift specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **SwiftFormat**: Auto-format `.swift` files after edit\n- **SwiftLint**: Run lint checks after editing `.swift` files\n- **swift build**: Type-check modified packages after edit\n\n## Warning\n\nFlag `print()` statements — use `os.Logger` or structured logging instead for production code.\n\n### swift/patterns\n\n# Swift Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Swift specific content.\n\n## Protocol-Oriented Design\n\nDefine small, focused protocols. Use protocol extensions for shared defaults:\n\n```swift\nprotocol Repository: Sendable {\n associatedtype Item: Identifiable & Sendable\n func find(by id: Item.ID) async throws -> Item?\n func save(_ item: Item) async throws\n}\n```\n\n## Value Types\n\n- Use structs for data transfer objects and models\n- Use enums with associated values to model distinct states:\n\n```swift\nenum LoadState<T: Sendable>: Sendable {\n case idle\n case loading\n case loaded(T)\n case failed(Error)\n}\n```\n\n## Actor Pattern\n\nUse actors for shared mutable state instead of locks or dispatch queues:\n\n```swift\nactor Cache<Key: Hashable & Sendable, Value: Sendable> {\n private var storage: [Key: Value] = [:]\n\n func get(_ key: Key) -> Value? { storage[key] }\n func set(_ key: Key, value: Value) { storage[key] = value }\n}\n```\n\n## Dependency Injection\n\nInject protocols with default parameters — production uses defaults, tests inject mocks:\n\n```swift\nstruct UserService {\n private let repository: any UserRepository\n\n init(repository: any UserRepository = DefaultUserRepository()) {\n self.repository = repository\n }\n}\n```\n\n## References\n\nSee skill: `swift-actor-persistence` for actor-based persistence patterns.\nSee skill: `swift-protocol-di-testing` for protocol-based DI and testing.\n\n### swift/security\n\n# Swift Security\n\n> This file extends [common/security.md](../common/security.md) with Swift specific content.\n\n## Secret Management\n\n- Use **Keychain Services** for sensitive data (tokens, passwords, keys) — never `UserDefaults`\n- Use environment variables or `.xcconfig` files for build-time secrets\n- Never hardcode secrets in source — decompilation tools extract them trivially\n\n```swift\nlet apiKey = ProcessInfo.processInfo.environment[\"API_KEY\"]\nguard let apiKey, !apiKey.isEmpty else {\n fatalError(\"API_KEY not configured\")\n}\n```\n\n## Transport Security\n\n- App Transport Security (ATS) is enforced by default — do not disable it\n- Use certificate pinning for critical endpoints\n- Validate all server certificates\n\n## Input Validation\n\n- Sanitize all user input before display to prevent injection\n- Use `URL(string:)` with validation rather than force-unwrapping\n- Validate data from external sources (APIs, deep links, pasteboard) before processing\n\n### swift/testing\n\n# Swift Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Swift specific content.\n\n## Framework\n\nUse **Swift Testing** (`import Testing`) for new tests. Use `@Test` and `#expect`:\n\n```swift\n@Test(\"User creation validates email\")\nfunc userCreationValidatesEmail() throws {\n #expect(throws: ValidationError.invalidEmail) {\n try User(email: \"not-an-email\")\n }\n}\n```\n\n## Test Isolation\n\nEach test gets a fresh instance — set up in `init`, tear down in `deinit`. No shared mutable state between tests.\n\n## Parameterized Tests\n\n```swift\n@Test(\"Validates formats\", arguments: [\"json\", \"xml\", \"csv\"])\nfunc validatesFormat(format: String) throws {\n let parser = try Parser(format: format)\n #expect(parser.isValid)\n}\n```\n\n## Coverage\n\n```bash\nswift test --enable-code-coverage\n```\n\n## Reference\n\nSee skill: `swift-protocol-di-testing` for protocol-based dependency injection and mock patterns with Swift Testing.\n\n### typescript/coding-style\n\n# TypeScript/JavaScript Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with TypeScript/JavaScript specific content.\n\n## Types and Interfaces\n\nUse types to make public APIs, shared models, and component props explicit, readable, and reusable.\n\n### Public APIs\n\n- Add parameter and return types to exported functions, shared utilities, and public class methods\n- Let TypeScript infer obvious local variable types\n- Extract repeated inline object shapes into named types or interfaces\n\n```typescript\n// WRONG: Exported function without explicit types\nexport function formatUser(user) {\n return `${user.firstName} ${user.lastName}`\n}\n\n// CORRECT: Explicit types on public APIs\ninterface User {\n firstName: string\n lastName: string\n}\n\nexport function formatUser(user: User): string {\n return `${user.firstName} ${user.lastName}`\n}\n```\n\n### Interfaces vs. Type Aliases\n\n- Use `interface` for object shapes that may be extended or implemented\n- Use `type` for unions, intersections, tuples, mapped types, and utility types\n- Prefer string literal unions over `enum` unless an `enum` is required for interoperability\n\n```typescript\ninterface User {\n id: string\n email: string\n}\n\ntype UserRole = 'admin' | 'member'\ntype UserWithRole = User & {\n role: UserRole\n}\n```\n\n### Avoid `any`\n\n- Avoid `any` in application code\n- Use `unknown` for external or untrusted input, then narrow it safely\n- Use generics when a value's type depends on the caller\n\n```typescript\n// WRONG: any removes type safety\nfunction getErrorMessage(error: any) {\n return error.message\n}\n\n// CORRECT: unknown forces safe narrowing\nfunction getErrorMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message\n }\n\n return 'Unexpected error'\n}\n```\n\n### React Props\n\n- Define component props with a named `interface` or `type`\n- Type callback props explicitly\n- Do not use `React.FC` unless there is a specific reason to do so\n\n```typescript\ninterface User {\n id: string\n email: string\n}\n\ninterface UserCardProps {\n user: User\n onSelect: (id: string) => void\n}\n\nfunction UserCard({ user, onSelect }: UserCardProps) {\n return <button onClick={() => onSelect(user.id)}>{user.email}</button>\n}\n```\n\n### JavaScript Files\n\n- In `.js` and `.jsx` files, use JSDoc when types improve clarity and a TypeScript migration is not practical\n- Keep JSDoc aligned with runtime behavior\n\n```javascript\n/**\n * @param {{ firstName: string, lastName: string }} user\n * @returns {string}\n */\nexport function formatUser(user) {\n return `${user.firstName} ${user.lastName}`\n}\n```\n\n## Immutability\n\nUse spread operator for immutable updates:\n\n```typescript\ninterface User {\n id: string\n name: string\n}\n\n// WRONG: Mutation\nfunction updateUser(user: User, name: string): User {\n user.name = name // MUTATION!\n return user\n}\n\n// CORRECT: Immutability\nfunction updateUser(user: Readonly<User>, name: string): User {\n return {\n ...user,\n name\n }\n}\n```\n\n## Error Handling\n\nUse async/await with try-catch and narrow unknown errors safely:\n\n```typescript\ninterface User {\n id: string\n email: string\n}\n\ndeclare function riskyOperation(userId: string): Promise<User>\n\nfunction getErrorMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message\n }\n\n return 'Unexpected error'\n}\n\nconst logger = {\n error: (message: string, error: unknown) => {\n // Replace with your production logger (for example, pino or winston).\n }\n}\n\nasync function loadUser(userId: string): Promise<User> {\n try {\n const result = await riskyOperation(userId)\n return result\n } catch (error: unknown) {\n logger.error('Operation failed', error)\n throw new Error(getErrorMessage(error))\n }\n}\n```\n\n## Input Validation\n\nUse Zod for schema-based validation and infer types from the schema:\n\n```typescript\nimport { z } from 'zod'\n\nconst userSchema = z.object({\n email: z.string().email(),\n age: z.number().int().min(0).max(150)\n})\n\ntype UserInput = z.infer<typeof userSchema>\n\nconst validated: UserInput = userSchema.parse(input)\n```\n\n## Console.log\n\n- No `console.log` statements in production code\n- Use proper logging libraries instead\n- See hooks for automatic detection\n\n### typescript/hooks\n\n# TypeScript/JavaScript Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with TypeScript/JavaScript specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **Prettier**: Auto-format JS/TS files after edit\n- **TypeScript check**: Run `tsc` after editing `.ts`/`.tsx` files\n- **console.log warning**: Warn about `console.log` in edited files\n\n## Stop Hooks\n\n- **console.log audit**: Check all modified files for `console.log` before session ends\n\n### typescript/patterns\n\n# TypeScript/JavaScript Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with TypeScript/JavaScript specific content.\n\n## API Response Format\n\n```typescript\ninterface ApiResponse<T> {\n success: boolean\n data?: T\n error?: string\n meta?: {\n total: number\n page: number\n limit: number\n }\n}\n```\n\n## Custom Hooks Pattern\n\n```typescript\nexport function useDebounce<T>(value: T, delay: number): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value)\n\n useEffect(() => {\n const handler = setTimeout(() => setDebouncedValue(value), delay)\n return () => clearTimeout(handler)\n }, [value, delay])\n\n return debouncedValue\n}\n```\n\n## Repository Pattern\n\n```typescript\ninterface Repository<T> {\n findAll(filters?: Filters): Promise<T[]>\n findById(id: string): Promise<T | null>\n create(data: CreateDto): Promise<T>\n update(id: string, data: UpdateDto): Promise<T>\n delete(id: string): Promise<void>\n}\n```\n\n### typescript/security\n\n# TypeScript/JavaScript Security\n\n> This file extends [common/security.md](../common/security.md) with TypeScript/JavaScript specific content.\n\n## Secret Management\n\n```typescript\n// NEVER: Hardcoded secrets\nconst apiKey = \"sk-proj-xxxxx\"\n\n// ALWAYS: Environment variables\nconst apiKey = process.env.API_KEY\n\nif (!apiKey) {\n throw new Error('API_KEY not configured')\n}\n```\n\n## Agent Support\n\n- Use **security-reviewer** skill for comprehensive security audits\n\n### typescript/testing\n\n# TypeScript/JavaScript Testing\n\n> This file extends [common/testing.md](../common/testing.md) with TypeScript/JavaScript specific content.\n\n## E2E Testing\n\nUse **Playwright** as the E2E testing framework for critical user flows.\n\n## Agent Support\n\n- **e2e-runner** - Playwright E2E testing specialist\n\n### vue/coding-style\n\n# Vue Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Vue specific content.\n\n## SFC Structure\n\n- Always `<script setup lang=\"ts\">` with the Composition API. No Options API in new code.\n- Block order inside a `.vue` file: `<script setup>`, then `<template>`, then `<style scoped>`. One component per file.\n- Naming: component files PascalCase (`AuctionCard.vue`), composables camelCase prefixed `useXxx` (`useAuctionTimer`).\n- Format with Prettier plus ESLint flat config using `eslint-plugin-vue` (`vue/vue3-recommended`). Type-check with `vue-tsc`.\n\n## Reactivity Discipline\n\n- `ref` is the primary state API. Mutate via `.value` in script, auto-unwrapped only at template top level.\n- Nested `ref` inside arrays, `Map`, or `Set` still needs `.value` to read.\n- Reach for `reactive` only for grouped object state. Never reassign a whole `reactive` object.\n- Never destructure a `reactive` object or a Pinia store without `toRefs` / `storeToRefs`. Plain destructure silently drops reactivity.\n\n## Computed and Watchers\n\n- `computed` getters must be pure: no side effects, no async, no DOM access.\n- 3.4+ `computed` only triggers when the returned value changes. Return the prior object unchanged when equal to skip downstream updates.\n- `watch` is lazy. Pass a getter for a reactive property (`watch(() => x.value, ...)`), not the bare reactive object.\n- `watchEffect` is eager and stops tracking dependencies after its first `await`.\n\n## Lifecycle and DOM\n\n- Register lifecycle hooks synchronously inside `setup` (`onMounted`, `onUnmounted`).\n- Clean up timers, listeners, and subscriptions in `onUnmounted`.\n- Read or measure the DOM only after `await nextTick()`.\n\n## Macros and Templates\n\n- Macros: `defineProps` / `defineEmits` (tuple form `change: [id: number]`), `defineModel` (3.4+) for `v-model`, `withDefaults` or 3.5+ reactive-props-destructure for defaults, `defineExpose` for the public ref API.\n- Put a `:key` on every `v-for`, a stable unique primitive. Never the array index, never an object.\n- Never put `v-if` and `v-for` on the same element. Wrap with `<template v-for>` plus an inner `v-if`, or precompute a filtered list.\n\n```vue\n<script setup lang=\"ts\">\nconst props = defineProps<{ id: number }>()\nconst emit = defineEmits<{ change: [id: number] }>()\nconst open = defineModel<boolean>('open', { default: false })\n</script>\n```\n\n## Reference\n\n- ECC skills: `frontend-patterns`, `vite-patterns`.\n- Docs: <https://vuejs.org/api/sfc-script-setup.html> · <https://vuejs.org/guide/essentials/reactivity-fundamentals.html> · <https://eslint.vuejs.org/>\n\n### vue/hooks\n\n# Vue Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Vue specific content.\n\n## PostToolUse Targets\n\nRun on `*.vue`, `*.ts`, and `*.tsx` after edits. Scope to changed files where possible.\n\n## Typecheck\n\n- Use `vue-tsc --noEmit` for SFC plus TypeScript checking. Plain `tsc` cannot read `.vue` single-file components, so it must not be the typecheck hook for this project.\n- Typecheck is project-wide. Debounce or scope it so a save-on-every-keystroke loop does not stall the editor.\n\n## Lint and Format\n\n- `eslint --fix` with `eslint-plugin-vue` (flat-config `vue/vue3-recommended`) covers both template and script lint.\n- `prettier --write` for formatting. Prefer Prettier-via-ESLint over a separate Prettier pass to avoid double formatting and fight loops.\n\n## Architecture Boundaries\n\n- Optional: enforce Feature-Sliced Design slice boundaries with `@feature-sliced/steiger` or `eslint-plugin-boundaries` to block deep cross-slice imports.\n\n## Sequencing\n\n```bash\n# changed files only\neslint --fix \"$FILE\"\nprettier --write \"$FILE\"\n# project-wide, debounced\nvue-tsc --noEmit\n```\n\n- Run lint and format per-file first, then the project-wide typecheck last so type errors reflect the formatted source.\n\n## Reference\n\n- ECC skills: `frontend-patterns`, `vite-patterns`.\n- Docs: <https://github.com/vuejs/language-tools> (vue-tsc) · <https://eslint.vuejs.org/> · <https://github.com/feature-sliced/steiger>\n\n### vue/patterns\n\n# Vue Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Vue specific content.\n\n## Composables\n\n- The composable (`useXxx`) is the reusable-logic unit. In Feature-Sliced Design it lives in the slice `model` segment.\n- Accept `MaybeRefOrGetter<T>` inputs and normalize with `toValue`, so callers can pass a ref, a getter, or a raw value.\n- Return `toRefs(reactive(...))` so consumers can destructure without losing reactivity.\n- A composable that uses lifecycle hooks or `provide` / `inject` must be called inside a component `setup`, not lazily or conditionally.\n\n## Props, Emits, v-model\n\n- Type-based `defineProps<Props>()` and tuple-form `defineEmits<{ change: [id: number] }>()`.\n- `defineModel<T>('name', { default })` for two-way binding. It compiles to a prop plus an `update:*` emit.\n\n## Provide / Inject\n\n- Use `provide` / `inject` for tree-scoped data without prop drilling.\n- Type-safe collision-free keys: `const key = Symbol() as InjectionKey<T>`.\n- The provider owns mutations. Expose a `readonly` ref plus an explicit updater function, never a raw mutable ref.\n\n## Pinia (FSD model segment)\n\n- Prefer setup stores: `ref` is state, `computed` is getters, `function` is actions.\n- Setup stores do not get `$reset` for free. Define your own.\n- Use `storeToRefs` for state and getters. Destructure actions directly off the store.\n- Never persist raw auth tokens to `localStorage`.\n\n## vue-router\n\n- Lazy-load route components with dynamic `import()`.\n- A global `beforeEach` auth gate keyed on `meta.requiresAuth`. Guards return `false` (cancel), a route location (redirect), or `undefined` / `true` (continue).\n- Watch `() => route.params.id`, not the whole `route` object.\n\n## vue-query (server cache)\n\n- `@tanstack/vue-query` owns server-cache state. Pinia owns client state.\n- Put request functions plus `queryOptions` factories in the FSD `api` segment.\n- Critical: put the ref or computed ITSELF in the query key, never `.value`. Passing `.value` freezes the key and kills reactive refetch.\n\n```ts\nuseQuery({ queryKey: ['auction', id], queryFn: () => fetchAuction(toValue(id)) })\n// after a mutation\nqueryClient.invalidateQueries({ queryKey: ['auction', id] })\n```\n\n## Reference\n\n- ECC skills: `frontend-patterns`, `vite-patterns`.\n- Docs: <https://pinia.vuejs.org/> · <https://router.vuejs.org/> · <https://tanstack.com/query/latest/docs/framework/vue/overview> · <https://vuejs.org/guide/reusability/composables.html>\n\n### vue/security\n\n# Vue Security\n\n> This file extends [common/security.md](../common/security.md) with Vue specific content.\n\n## What Vue Escapes Automatically\n\n- Text interpolation `{{ }}` and dynamic attribute bindings (`:title`) are auto-escaped. The vectors below are NOT protected.\n\n## Rule No.1: Templates from Trusted Sources Only\n\n- Never use non-trusted content as a component template. No runtime template compilation from user input.\n- No user-controlled `:is` that resolves a component from an arbitrary string.\n\n## v-html and Render Functions\n\n- `v-html` bypasses escaping and is a direct XSS vector. Avoid it on user content.\n- If unavoidable, sanitize with DOMPurify (allowlist config) before binding, or render in a sandboxed iframe. Vue itself recommends sanitizing on the backend before persisting.\n- Render-function and scoped-slot output carry the same risk. Passing user HTML through `h()` with `innerHTML` is `v-html` by another name. Sanitize first.\n\n## URL, Style, and Event Injection\n\n- `:href` and `:src` are not escaped. `javascript:` URLs execute. Validate the scheme, allow `http` / `https` / `mailto` only. Vue docs reference `@braintree/sanitize-url`, but sanitize on the backend before persisting.\n- `:style` with user input is unsafe (CSS exfiltration). Use object syntax with whitelisted properties, never a raw user string.\n- Never bind user input to `onclick`, `onfocus`, or any event attribute.\n\n## Client Bundle Secrets\n\n- Anything in `import.meta.env.VITE_*` ships to the browser. Keep API keys and tokens server-side.\n- Use httpOnly cookies for session tokens. Never bundle credentials into the client.\n\n```vue\n<!-- unsafe -->\n<div v-html=\"userBio\" />\n<!-- safe -->\n<div v-html=\"sanitize(userBio)\" />\n```\n\n## Reference\n\n- ECC skills: `frontend-patterns`, `vite-patterns`.\n- Docs: <https://vuejs.org/guide/best-practices/security.html> · <https://github.com/cure53/DOMPurify> · <https://github.com/braintree/sanitize-url>\n\n### vue/testing\n\n# Vue Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Vue specific content.\n\n## Stack\n\n- Vitest (Vite-native runner) plus `@vue/test-utils`. `create-vue` scaffolds `@vitejs/plugin-vue`.\n- DOM environment via `happy-dom` or `jsdom`, set in `vite.config.ts` under `test.environment`.\n\n## Rendering and Async\n\n- `mount` for a full render. `shallowMount` to stub all child components.\n- `trigger` and `setValue` return promises, `await` them.\n- `flushPromises` flushes resolved promise handlers. `nextTick` settles the DOM after a state change.\n\n## What to Test\n\n- Test the public interface only: props, emitted events, slots, rendered output.\n- Do not assert private state or internal methods, and do not rely solely on snapshots.\n\n## Composables\n\n- Composables that use only reactivity APIs unit-test directly: call the function, assert on the returned refs.\n- Composables that use lifecycle hooks or `inject` must be tested through a host component.\n\n## Pinia\n\n- In components: `createTestingPinia()` from `@pinia/testing`, passed via `global.plugins`. Actions are stubbed by default, set `stubActions: false` to run them. `createSpy: vi.fn` is required under Vitest (no Jest globals).\n- In isolation: `beforeEach(() => setActivePinia(createPinia()))` gives a fresh store per test and prevents state leakage.\n\n## Mount Config\n\n- `global.plugins`, `global.stubs` (stubs `Transition` / `TransitionGroup` by default), `global.mocks` (e.g. `$router`), `global.provide` (for `inject`, Symbol keys supported).\n- `RouterLinkStub` stubs `router-link` without mounting a full router.\n\n```ts\nconst wrapper = mount(AuctionCard, {\n props: { id: 1 },\n global: { plugins: [createTestingPinia({ createSpy: vi.fn })] },\n})\nawait wrapper.find('button').trigger('click')\nexpect(wrapper.emitted('bid')).toBeTruthy()\n```\n\n## Reference\n\n- ECC skills: `frontend-patterns`, `vite-patterns`.\n- Docs: <https://test-utils.vuejs.org/api/> · <https://pinia.vuejs.org/cookbook/testing.html> · <https://vitest.dev/>\n\n### web/coding-style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with web-specific frontend content.\n\n# Web Coding Style\n\n## File Organization\n\nOrganize by feature or surface area, not by file type:\n\n```text\nsrc/\n├── components/\n│ ├── hero/\n│ │ ├── Hero.tsx\n│ │ ├── HeroVisual.tsx\n│ │ └── hero.css\n│ ├── scrolly-section/\n│ │ ├── ScrollySection.tsx\n│ │ ├── StickyVisual.tsx\n│ │ └── scrolly.css\n│ └── ui/\n│ ├── Button.tsx\n│ ├── SurfaceCard.tsx\n│ └── AnimatedText.tsx\n├── hooks/\n│ ├── useReducedMotion.ts\n│ └── useScrollProgress.ts\n├── lib/\n│ ├── animation.ts\n│ └── color.ts\n└── styles/\n ├── tokens.css\n ├── typography.css\n └── global.css\n```\n\n## CSS Custom Properties\n\nDefine design tokens as variables. Do not hardcode palette, typography, or spacing repeatedly:\n\n```css\n:root {\n --color-surface: oklch(98% 0 0);\n --color-text: oklch(18% 0 0);\n --color-accent: oklch(68% 0.21 250);\n\n --text-base: clamp(1rem, 0.92rem + 0.4vw, 1.125rem);\n --text-hero: clamp(3rem, 1rem + 7vw, 8rem);\n\n --space-section: clamp(4rem, 3rem + 5vw, 10rem);\n\n --duration-fast: 150ms;\n --duration-normal: 300ms;\n --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);\n}\n```\n\n## Animation-Only Properties\n\nPrefer compositor-friendly motion:\n- `transform`\n- `opacity`\n- `clip-path`\n- `filter` (sparingly)\n\nAvoid animating layout-bound properties:\n- `width`\n- `height`\n- `top`\n- `left`\n- `margin`\n- `padding`\n- `border`\n- `font-size`\n\n## Semantic HTML First\n\n```html\n<header>\n <nav aria-label=\"Main navigation\">...</nav>\n</header>\n<main>\n <section aria-labelledby=\"hero-heading\">\n <h1 id=\"hero-heading\">...</h1>\n </section>\n</main>\n<footer>...</footer>\n```\n\nDo not reach for generic wrapper `div` stacks when a semantic element exists.\n\n## Naming\n\n- Components: PascalCase (`ScrollySection`, `SurfaceCard`)\n- Hooks: `use` prefix (`useReducedMotion`)\n- CSS classes: kebab-case or utility classes\n- Animation timelines: camelCase with intent (`heroRevealTl`)\n\n### web/design-quality\n\n> This file extends [common/patterns.md](../common/patterns.md) with web-specific design-quality guidance.\n\n# Web Design Quality Standards\n\n## Anti-Template Policy\n\nDo not ship generic template-looking UI. Frontend output should look intentional, opinionated, and specific to the product.\n\n### Banned Patterns\n\n- Default card grids with uniform spacing and no hierarchy\n- Stock hero section with centered headline, gradient blob, and generic CTA\n- Unmodified library defaults passed off as finished design\n- Flat layouts with no layering, depth, or motion\n- Uniform radius, spacing, and shadows across every component\n- Safe gray-on-white styling with one decorative accent color\n- Dashboard-by-numbers layouts with sidebar + cards + charts and no point of view\n- Default font stacks used without a deliberate reason\n\n### Required Qualities\n\nEvery meaningful frontend surface should demonstrate at least four of these:\n\n1. Clear hierarchy through scale contrast\n2. Intentional rhythm in spacing, not uniform padding everywhere\n3. Depth or layering through overlap, shadows, surfaces, or motion\n4. Typography with character and a real pairing strategy\n5. Color used semantically, not just decoratively\n6. Hover, focus, and active states that feel designed\n7. Grid-breaking editorial or bento composition where appropriate\n8. Texture, grain, or atmosphere when it fits the visual direction\n9. Motion that clarifies flow instead of distracting from it\n10. Data visualization treated as part of the design system, not an afterthought\n\n## Before Writing Frontend Code\n\n1. Pick a specific style direction. Avoid vague defaults like \"clean minimal\".\n2. Define a palette intentionally.\n3. Choose typography deliberately.\n4. Gather at least a small set of real references.\n5. Use ECC design/frontend skills where relevant.\n\n## Worthwhile Style Directions\n\n- Editorial / magazine\n- Neo-brutalism\n- Glassmorphism with real depth\n- Dark luxury or light luxury with disciplined contrast\n- Bento layouts\n- Scrollytelling\n- 3D integration\n- Swiss / International\n- Retro-futurism\n\nDo not default to dark mode automatically. Choose the visual direction the product actually wants.\n\n## Component Checklist\n\n- [ ] Does it avoid looking like a default Tailwind or shadcn template?\n- [ ] Does it have intentional hover/focus/active states?\n- [ ] Does it use hierarchy rather than uniform emphasis?\n- [ ] Would this look believable in a real product screenshot?\n- [ ] If it supports both themes, do both light and dark feel intentional?\n\n### web/hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with web-specific hook recommendations.\n\n# Web Hooks\n\n## Recommended PostToolUse Hooks\n\nPrefer project-local tooling. Do not wire hooks to remote one-off package execution.\n\n### Format on Save\n\nUse the project's existing formatter entrypoint after edits:\n\n```json\n{\n \"hooks\": {\n \"PostToolUse\": [\n {\n \"matcher\": \"Write|Edit\",\n \"command\": \"pnpm prettier --write \\\"$FILE_PATH\\\"\",\n \"description\": \"Format edited frontend files\"\n }\n ]\n }\n}\n```\n\nEquivalent local commands via `yarn prettier` or `npm exec prettier --` are fine when they use repo-owned dependencies.\n\n### Lint Check\n\n```json\n{\n \"hooks\": {\n \"PostToolUse\": [\n {\n \"matcher\": \"Write|Edit\",\n \"command\": \"pnpm eslint --fix \\\"$FILE_PATH\\\"\",\n \"description\": \"Run ESLint on edited frontend files\"\n }\n ]\n }\n}\n```\n\n### Type Check\n\nUse `--incremental` so re-runs reuse the previous `.tsbuildinfo` (1-3s on unchanged code instead of 30-60s every time). Wrap in `timeout` so a stuck tsc gets reaped by the OS instead of accumulating across edits — this prevents the multi-process buildup that happens when edits fire faster than tsc finishes.\n\n```json\n{\n \"hooks\": {\n \"PostToolUse\": [\n {\n \"matcher\": \"Write|Edit\",\n \"command\": \"timeout 60 pnpm tsc --noEmit --pretty false --incremental --tsBuildInfoFile node_modules/.cache/tsc-hook.tsbuildinfo\",\n \"description\": \"Type-check after frontend edits (incremental + timeout-capped)\"\n }\n ]\n }\n}\n```\n\n**Why both flags matter:**\n- Without `--incremental`, every edit re-checks the entire program from scratch. On a real Next.js project this stacks fast: edits at 5-10s intervals + 30-60s tsc runs = N concurrent tsc processes.\n- Without `timeout`, a tsc that hangs (transitive dep change, type-checker stuck on a recursive type) never exits and orphans when the parent shell does.\n- `--tsBuildInfoFile` is required because `--noEmit` normally suppresses the buildinfo write; specifying the path explicitly keeps incremental working.\n\nIf you're on Windows without GNU coreutils, swap `timeout 60` for a PowerShell wrapper or rely on a Stop/SessionEnd hook to sweep stale tsc processes.\n\n### CSS Lint\n\n```json\n{\n \"hooks\": {\n \"PostToolUse\": [\n {\n \"matcher\": \"Write|Edit\",\n \"command\": \"pnpm stylelint --fix \\\"$FILE_PATH\\\"\",\n \"description\": \"Lint edited stylesheets\"\n }\n ]\n }\n}\n```\n\n## PreToolUse Hooks\n\n### Guard File Size\n\nBlock oversized writes from tool input content, not from a file that may not exist yet:\n\n```json\n{\n \"hooks\": {\n \"PreToolUse\": [\n {\n \"matcher\": \"Write\",\n \"command\": \"node -e \\\"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const c=i.tool_input?.content||'';const lines=c.split('\\\\n').length;if(lines>800){console.error('[Hook] BLOCKED: File exceeds 800 lines ('+lines+' lines)');console.error('[Hook] Split into smaller modules');process.exit(2)}console.log(d)})\\\"\",\n \"description\": \"Block writes that exceed 800 lines\"\n }\n ]\n }\n}\n```\n\n## Stop Hooks\n\n### Final Build Verification\n\n```json\n{\n \"hooks\": {\n \"Stop\": [\n {\n \"command\": \"pnpm build\",\n \"description\": \"Verify the production build at session end\"\n }\n ]\n }\n}\n```\n\n## Ordering\n\nRecommended order:\n1. format\n2. lint\n3. type check\n4. build verification\n\n### web/patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with web-specific patterns.\n\n# Web Patterns\n\n## Component Composition\n\n### Compound Components\n\nUse compound components when related UI shares state and interaction semantics:\n\n```tsx\n<Tabs defaultValue=\"overview\">\n <Tabs.List>\n <Tabs.Trigger value=\"overview\">Overview</Tabs.Trigger>\n <Tabs.Trigger value=\"settings\">Settings</Tabs.Trigger>\n </Tabs.List>\n <Tabs.Content value=\"overview\">...</Tabs.Content>\n <Tabs.Content value=\"settings\">...</Tabs.Content>\n</Tabs>\n```\n\n- Parent owns state\n- Children consume via context\n- Prefer this over prop drilling for complex widgets\n\n### Render Props / Slots\n\n- Use render props or slot patterns when behavior is shared but markup must vary\n- Keep keyboard handling, ARIA, and focus logic in the headless layer\n\n### Container / Presentational Split\n\n- Container components own data loading and side effects\n- Presentational components receive props and render UI\n- Presentational components should stay pure\n\n## State Management\n\nTreat these separately:\n\n| Concern | Tooling |\n|---------|---------|\n| Server state | TanStack Query, SWR, tRPC |\n| Client state | Zustand, Jotai, signals |\n| URL state | search params, route segments |\n| Form state | React Hook Form or equivalent |\n\n- Do not duplicate server state into client stores\n- Derive values instead of storing redundant computed state\n\n## URL As State\n\nPersist shareable state in the URL:\n- filters\n- sort order\n- pagination\n- active tab\n- search query\n\n## Data Fetching\n\n### Stale-While-Revalidate\n\n- Return cached data immediately\n- Revalidate in the background\n- Prefer existing libraries instead of rolling this by hand\n\n### Optimistic Updates\n\n- Snapshot current state\n- Apply optimistic update\n- Roll back on failure\n- Emit visible error feedback when rolling back\n\n### Parallel Loading\n\n- Fetch independent data in parallel\n- Avoid parent-child request waterfalls\n- Prefetch likely next routes or states when justified\n\n### web/performance\n\n> This file extends [common/performance.md](../common/performance.md) with web-specific performance content.\n\n# Web Performance Rules\n\n## Core Web Vitals Targets\n\n| Metric | Target |\n|--------|--------|\n| LCP | < 2.5s |\n| INP | < 200ms |\n| CLS | < 0.1 |\n| FCP | < 1.5s |\n| TBT | < 200ms |\n\n## Bundle Budget\n\n| Page Type | JS Budget (gzipped) | CSS Budget |\n|-----------|---------------------|------------|\n| Landing page | < 150kb | < 30kb |\n| App page | < 300kb | < 50kb |\n| Microsite | < 80kb | < 15kb |\n\n## Loading Strategy\n\n1. Inline critical above-the-fold CSS where justified\n2. Preload the hero image and primary font only\n3. Defer non-critical CSS or JS\n4. Dynamically import heavy libraries\n\n```js\nconst gsapModule = await import('gsap');\nconst { ScrollTrigger } = await import('gsap/ScrollTrigger');\n```\n\n## Image Optimization\n\n- Explicit `width` and `height`\n- `loading=\"eager\"` plus `fetchpriority=\"high\"` for hero media only\n- `loading=\"lazy\"` for below-the-fold assets\n- Prefer AVIF or WebP with fallbacks\n- Never ship source images far beyond rendered size\n\n## Font Loading\n\n- Max two font families unless there is a clear exception\n- `font-display: swap`\n- Subset where possible\n- Preload only the truly critical weight/style\n\n## Animation Performance\n\n- Animate compositor-friendly properties only\n- Use `will-change` narrowly and remove it when done\n- Prefer CSS for simple transitions\n- Use `requestAnimationFrame` or established animation libraries for JS motion\n- Avoid scroll handler churn; use IntersectionObserver or well-behaved libraries\n\n## Performance Checklist\n\n- [ ] All images have explicit dimensions\n- [ ] No accidental render-blocking resources\n- [ ] No layout shifts from dynamic content\n- [ ] Motion stays on compositor-friendly properties\n- [ ] Third-party scripts load async/defer and only when needed\n\n### web/security\n\n> This file extends [common/security.md](../common/security.md) with web-specific security content.\n\n# Web Security Rules\n\n## Content Security Policy\n\nAlways configure a production CSP.\n\n### Nonce-Based CSP\n\nUse a per-request nonce for scripts instead of `'unsafe-inline'`.\n\n```text\nContent-Security-Policy:\n default-src 'self';\n script-src 'self' 'nonce-{RANDOM}' https://cdn.jsdelivr.net;\n style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;\n img-src 'self' data: https:;\n font-src 'self' https://fonts.gstatic.com;\n connect-src 'self' https://*.example.com;\n frame-src 'none';\n object-src 'none';\n base-uri 'self';\n```\n\nAdjust origins to the project. Do not cargo-cult this block unchanged.\n\n## XSS Prevention\n\n- Never inject unsanitized HTML\n- Avoid `innerHTML` / `dangerouslySetInnerHTML` unless sanitized first\n- Escape dynamic template values\n- Sanitize user HTML with a vetted local sanitizer when absolutely necessary\n\n## Third-Party Scripts\n\n- Load asynchronously\n- Use SRI when serving from a CDN\n- Audit quarterly\n- Prefer self-hosting for critical dependencies when practical\n\n## HTTPS and Headers\n\n```text\nStrict-Transport-Security: max-age=31536000; includeSubDomains; preload\nX-Content-Type-Options: nosniff\nX-Frame-Options: DENY\nReferrer-Policy: strict-origin-when-cross-origin\nPermissions-Policy: camera=(), microphone=(), geolocation=()\n```\n\n## Forms\n\n- CSRF protection on state-changing forms\n- Rate limiting on submission endpoints\n- Validate client and server side\n- Prefer honeypots or light anti-abuse controls over heavy-handed CAPTCHA defaults\n\n### web/testing\n\n> This file extends [common/testing.md](../common/testing.md) with web-specific testing content.\n\n# Web Testing Rules\n\n## Priority Order\n\n### 1. Visual Regression\n\n- Screenshot key breakpoints: 320, 768, 1024, 1440\n- Test hero sections, scrollytelling sections, and meaningful states\n- Use Playwright screenshots for visual-heavy work\n- If both themes exist, test both\n\n### 2. Accessibility\n\n- Run automated accessibility checks\n- Test keyboard navigation\n- Verify reduced-motion behavior\n- Verify color contrast\n\n### 3. Performance\n\n- Run Lighthouse or equivalent against meaningful pages\n- Keep CWV targets from [performance.md](performance.md)\n\n### 4. Cross-Browser\n\n- Minimum: Chrome, Firefox, Safari\n- Test scrolling, motion, and fallback behavior\n\n### 5. Responsive\n\n- Test 320, 375, 768, 1024, 1440, 1920\n- Verify no overflow\n- Verify touch interactions\n\n## E2E Shape\n\n```ts\nimport { test, expect } from '@playwright/test';\n\ntest('landing hero loads', async ({ page }) => {\n await page.goto('/');\n await expect(page.locator('h1')).toBeVisible();\n});\n```\n\n- Avoid flaky timeout-based assertions\n- Prefer deterministic waits\n\n## Unit Tests\n\n- Test utilities, data transforms, and custom hooks\n- For highly visual components, visual regression often carries more signal than brittle markup assertions\n- Visual regression supplements coverage targets; it does not replace them\n"
|
|
13
|
+
"markdown": "# Full harness — the complete engineering toolkit\n\n<!-- adapted from affaan-m/ECC (MIT) — https://github.com/affaan-m/ECC -->\n\nThe maximal harness: 278 skills, 67 agents, 94 commands, and the full rules corpus below — every discipline, role, and command in one installable pack. Non-portable plugin-bootstrap hooks are intentionally omitted (they wrap a node -e loader that only runs inside its origin install); everything else is included unabridged.\n\n## Rules corpus\n\n\n### README\n\n# Rules\n\n## Structure\n\nRules are organized into a **common** layer plus **language-specific** directories:\n\n```\nrules/\n├── common/ # Language-agnostic principles (always install)\n│ ├── coding-style.md\n│ ├── git-workflow.md\n│ ├── testing.md\n│ ├── performance.md\n│ ├── patterns.md\n│ ├── hooks.md\n│ ├── agents.md\n│ └── security.md\n├── typescript/ # TypeScript/JavaScript specific\n├── angular/ # Angular specific\n├── vue/ # Vue 3 specific\n├── nuxt/ # Nuxt 4 specific\n├── python/ # Python specific\n├── golang/ # Go specific\n├── web/ # Web and frontend specific\n├── react-native/ # React Native / Expo specific\n├── swift/ # Swift specific\n├── php/ # PHP specific\n├── ruby/ # Ruby / Rails specific\n└── arkts/ # HarmonyOS / ArkTS specific\n```\n\n- **common/** contains universal principles — no language-specific code examples.\n- **Language directories** extend the common rules with framework-specific patterns, tools, and code examples. Each file references its common counterpart.\n\n## Installation\n\n### Option 1: Install Script (Recommended)\n\n```bash\n# Install common + one or more language-specific rule sets\n./install.sh typescript\n./install.sh angular\n./install.sh vue\n./install.sh nuxt\n./install.sh python\n./install.sh golang\n./install.sh web\n./install.sh react-native\n./install.sh swift\n./install.sh php\n./install.sh ruby\n./install.sh arkts\n\n# Install multiple languages at once\n./install.sh typescript python\n```\n\n### Option 2: Manual Installation\n\n> **Important:** Copy entire directories — do NOT flatten with `/*`.\n> Common and language-specific directories contain files with the same names.\n> Flattening them into one directory causes language-specific files to overwrite\n> common rules, and breaks the relative `../common/` references used by\n> language-specific files.\n>\n> Use the ECC-owned namespace below for user-level Claude installs. Flat\n> package-level destinations can collide with non-ECC rule packs and do not\n> match the main README guidance.\n\n```bash\n# Create the ECC rule namespace once.\nmkdir -p ~/.claude/rules/ecc\n\n# Install common rules (required for all projects)\ncp -r rules/common ~/.claude/rules/ecc/\n\n# Install language-specific rules based on your project's tech stack\ncp -r rules/typescript ~/.claude/rules/ecc/\ncp -r rules/angular ~/.claude/rules/ecc/\ncp -r rules/vue ~/.claude/rules/ecc/\ncp -r rules/nuxt ~/.claude/rules/ecc/\ncp -r rules/python ~/.claude/rules/ecc/\ncp -r rules/golang ~/.claude/rules/ecc/\ncp -r rules/web ~/.claude/rules/ecc/\ncp -r rules/react-native ~/.claude/rules/ecc/\ncp -r rules/swift ~/.claude/rules/ecc/\ncp -r rules/php ~/.claude/rules/ecc/\ncp -r rules/ruby ~/.claude/rules/ecc/\ncp -r rules/arkts ~/.claude/rules/ecc/\n\n# Attention ! ! ! Configure according to your actual project requirements; the configuration here is for reference only.\n```\n\nFor project-local rules, use the same namespace under the project root:\n\n```bash\nmkdir -p .claude/rules/ecc\ncp -r rules/common .claude/rules/ecc/\ncp -r rules/typescript .claude/rules/ecc/\n```\n\n## Rules vs Skills\n\n- **Rules** define standards, conventions, and checklists that apply broadly (e.g., \"80% test coverage\", \"no hardcoded secrets\").\n- **Skills** (`skills/` directory) provide deep, actionable reference material for specific tasks (e.g., `python-patterns`, `golang-testing`).\n\nLanguage-specific rule files reference relevant skills where appropriate. Rules tell you _what_ to do; skills tell you _how_ to do it.\n\n## Adding a New Language\n\nTo add support for a new language (e.g., `rust/`):\n\n1. Create a `rules/rust/` directory\n2. Add files that extend the common rules:\n - `coding-style.md` — formatting tools, idioms, error handling patterns\n - `testing.md` — test framework, coverage tools, test organization\n - `patterns.md` — language-specific design patterns\n - `hooks.md` — PostToolUse hooks for formatters, linters, type checkers\n - `security.md` — secret management, security scanning tools\n3. Each file should start with:\n ```\n > This file extends [common/xxx.md](../common/xxx.md) with <Language> specific content.\n ```\n4. Reference existing skills if available, or create new ones under `skills/`.\n\nFor non-language domains like `web/`, follow the same layered pattern when there is enough reusable domain-specific guidance to justify a standalone ruleset.\n\n## Rule Priority\n\nWhen language-specific rules and common rules conflict, **language-specific rules take precedence** (specific overrides general). This follows the standard layered configuration pattern (similar to CSS specificity or `.gitignore` precedence).\n\n- `rules/common/` defines universal defaults applicable to all projects.\n- `rules/golang/`, `rules/python/`, `rules/swift/`, `rules/php/`, `rules/typescript/`, `rules/react-native/`, etc. override those defaults where language idioms differ.\n\n### Example\n\n`common/coding-style.md` recommends immutability as a default principle. A language-specific `golang/coding-style.md` can override this:\n\n> Idiomatic Go uses pointer receivers for struct mutation — see [common/coding-style.md](../common/coding-style.md) for the general principle, but Go-idiomatic mutation is preferred here.\n\n### Common rules with override notes\n\nRules in `rules/common/` that may be overridden by language-specific files are marked with:\n\n> **Language note**: This rule may be overridden by language-specific rules for languages where this pattern is not idiomatic.\n\n### angular/coding-style\n\n# Angular Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Angular specific content.\n\n## Version Awareness\n\nAlways check the project's Angular version before writing code — features differ significantly between versions. Run `ng version` or inspect `package.json`. When creating a new project, do not pin a version unless the user specifies one.\n\nAfter generating or modifying Angular code, always run `ng build` to catch errors before finishing.\n\n## File Naming\n\nFollow Angular CLI conventions — one artifact per file:\n\n- `user-profile.component.ts` + `user-profile.component.html` + `user-profile.component.spec.ts`\n- `user.service.ts`, `auth.guard.ts`, `date-format.pipe.ts`\n- Feature folders: `features/users/`, `features/auth/`\n- Generate with the CLI: `ng generate component features/users/user-card`\n\n## Components\n\nPrefer standalone components (v17+ default). Use `OnPush` change detection on all new components.\n\n```typescript\n@Component({\n selector: 'app-user-card',\n standalone: true,\n imports: [RouterModule],\n templateUrl: './user-card.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class UserCardComponent {\n user = input.required<User>();\n select = output<string>();\n}\n```\n\n## Dependency Injection\n\nUse `inject()` over constructor injection. Keep constructors empty or remove them entirely.\n\n```typescript\n// CORRECT\n@Injectable({ providedIn: 'root' })\nexport class UserService {\n private http = inject(HttpClient);\n private router = inject(Router);\n}\n\n// WRONG: Constructor injection is verbose and harder to tree-shake\nconstructor(private http: HttpClient, private router: Router) {}\n```\n\nUse `InjectionToken` for non-class dependencies:\n\n```typescript\nconst API_URL = new InjectionToken<string>('API_URL');\n\n// Provide:\n{ provide: API_URL, useValue: 'https://api.example.com' }\n\n// Consume:\nprivate apiUrl = inject(API_URL);\n```\n\n## Signals\n\n### Core Primitives\n\n```typescript\ncount = signal(0);\ndoubled = computed(() => this.count() * 2);\n\nincrement() {\n this.count.update(n => n + 1);\n}\n```\n\n### `linkedSignal` — Writable Derived State\n\nUse `linkedSignal` when a signal must reset or adapt when a source changes, but also be independently writable:\n\n```typescript\nselectedOption = linkedSignal(() => this.options()[0]);\n// Resets to first option when options changes, but user can override\n```\n\n### `resource` — Async Data into Signals\n\nUse `resource()` to fetch async data reactively without manual subscriptions:\n\n```typescript\nuserResource = resource({\n request: () => ({ id: this.userId() }),\n loader: ({ request }) => fetch(`/api/users/${request.id}`).then(r => r.json()),\n});\n\n// Access: userResource.value(), userResource.isLoading(), userResource.error()\n```\n\n### `effect` Usage\n\nUse `effect()` only for side effects that must react to signal changes (logging, third-party DOM manipulation). Never use effects to synchronize signals — use `computed` or `linkedSignal` instead. For DOM work after render, use `afterRenderEffect`.\n\n```typescript\n// CORRECT: Side effect\neffect(() => console.log('User changed:', this.user()));\n\n// WRONG: Use computed instead\neffect(() => { this.fullName.set(`${this.first()} ${this.last()}`); });\n```\n\n## Templates\n\nUse v17+ block syntax. Always provide `track` in `@for`:\n\n```html\n@for (item of items(); track item.id) {\n <app-item [item]=\"item\" />\n}\n\n@if (isLoading()) {\n <app-spinner />\n} @else if (error()) {\n <app-error [message]=\"error()\" />\n} @else {\n <app-content [data]=\"data()\" />\n}\n```\n\nNo logic in templates beyond simple conditionals — move to component methods or pipes.\n\n## Forms\n\nChoose the form strategy that matches the project's existing approach:\n\n- **Signal Forms** (v21+): Preferred for new projects on v21+. Signal-based form state.\n- **Reactive Forms**: `FormBuilder` + `FormGroup` + `FormControl`. Best for complex forms with dynamic validation.\n- **Template-Driven Forms**: `ngModel`. Suitable for simple forms only.\n\n```typescript\n// Reactive Forms — standard approach for most apps\nexport class LoginComponent {\n private fb = inject(FormBuilder);\n\n form = this.fb.group({\n email: ['', [Validators.required, Validators.email]],\n password: ['', [Validators.required, Validators.minLength(8)]],\n });\n\n submit() {\n if (this.form.valid) {\n // use this.form.value\n }\n }\n}\n```\n\n## Component Styles\n\nUse component-level styles with `ViewEncapsulation.Emulated` (default). Avoid `ViewEncapsulation.None` unless building a design system that intentionally bleeds styles.\n\n- Scope styles to the component — do not use global class names inside component stylesheets\n- Use `:host` for host element styling\n- Prefer CSS custom properties for themeable values\n\n## Change Detection\n\n- Default to `ChangeDetectionStrategy.OnPush` on all new components\n- Signals and `async` pipe handle detection automatically — avoid `markForCheck()` and `detectChanges()`\n- Never mutate `@Input()` objects in place when using OnPush\n\n### angular/hooks\n\n# Angular Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Angular specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **Prettier**: Auto-format `.ts` and `.html` files after edit\n- **ESLint / ng lint**: Run `ng lint` after editing Angular source files to catch decorator misuse, template errors, and style violations\n- **TypeScript check**: Run `tsc --noEmit` after editing `.ts` files\n- **Build check**: Run `ng build` after generating or significantly changing Angular code to catch template and type errors early\n\n## Stop Hooks\n\n- **Lint audit**: Run `ng lint` across modified files before session ends to catch any outstanding violations\n\n### angular/patterns\n\n# Angular Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Angular specific content.\n\n## Smart / Dumb Component Split\n\nSmart (container) components own data fetching and state. Dumb (presentational) components receive inputs and emit outputs only — no service injection.\n\n```typescript\n// Smart — owns data\n@Component({ standalone: true, changeDetection: ChangeDetectionStrategy.OnPush })\nexport class UserPageComponent {\n private userService = inject(UserService);\n user = toSignal(this.userService.getUser(this.userId));\n}\n```\n\n```html\n<!-- Dumb — pure presentation -->\n<app-user-card [user]=\"user()\" (select)=\"onSelect($event)\" />\n```\n\n## Service Layer\n\nServices own all data access and business logic. Components delegate — no `HttpClient` in components.\n\n```typescript\n@Injectable({ providedIn: 'root' })\nexport class UserService {\n private http = inject(HttpClient);\n\n getUsers(): Observable<User[]> {\n return this.http.get<User[]>('/api/users');\n }\n}\n```\n\n## Async Data with `resource`\n\nUse `resource()` for reactive async fetching. Prefer over manual RxJS pipelines for simple data loading:\n\n```typescript\nexport class UserDetailComponent {\n userId = input.required<string>();\n\n userResource = resource({\n request: () => ({ id: this.userId() }),\n loader: ({ request }) =>\n firstValueFrom(inject(UserService).getUser(request.id)),\n });\n}\n```\n\nAccess state: `userResource.value()`, `userResource.isLoading()`, `userResource.error()`, `userResource.reload()`.\n\n## Signal State Patterns\n\n```typescript\n// Local mutable state\ncount = signal(0);\n\n// Derived (never duplicated)\ndoubled = computed(() => this.count() * 2);\n\n// Writable derived state that resets with source\nselectedItem = linkedSignal(() => this.items()[0]);\n\n// Bridge Observable to signal\nusers = toSignal(this.userService.getUsers(), { initialValue: [] });\n```\n\nNever store derived values in separate signals — use `computed`. Never use `effect` to sync signals — use `computed` or `linkedSignal`.\n\n## Subscription Cleanup\n\nUse `takeUntilDestroyed()` for all manual subscriptions. Never use manual `ngOnDestroy` + `Subject` + `takeUntil` on new code.\n\n```typescript\nexport class UserComponent {\n private destroyRef = inject(DestroyRef);\n\n ngOnInit() {\n this.userService.updates$\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(update => this.handleUpdate(update));\n }\n}\n```\n\n## Routing\n\n### Route Definition\n\n```typescript\n// app.routes.ts\nexport const routes: Routes = [\n { path: '', component: HomeComponent },\n {\n path: 'admin',\n canMatch: [authGuard], // CanMatch prevents loading the chunk at all\n loadChildren: () => import('./admin/admin.routes').then(m => m.ADMIN_ROUTES),\n },\n {\n path: 'users/:id',\n resolve: { user: userResolver },\n component: UserDetailComponent,\n },\n];\n```\n\n- Use `canMatch` over `canActivate` when the route module should not load for unauthorized users\n- Lazy-load all feature modules with `loadChildren`\n- Pre-fetch data with `resolve` to avoid loading states in components\n\n### Functional Guards\n\n```typescript\nexport const authGuard: CanActivateFn = () => {\n const auth = inject(AuthService);\n return auth.isAuthenticated()\n ? true\n : inject(Router).createUrlTree(['/login']);\n};\n```\n\n### Data Resolvers\n\n```typescript\nexport const userResolver: ResolveFn<User> = (route) => {\n return inject(UserService).getUser(route.paramMap.get('id')!);\n};\n```\n\n### View Transitions\n\nEnable smooth route transitions with the View Transitions API:\n\n```typescript\n// app.config.ts\nprovideRouter(routes, withViewTransitions())\n```\n\n## Dependency Injection Patterns\n\n### Scoped Providers\n\nProvide services at component or route level when they should not be singletons:\n\n```typescript\n@Component({\n providers: [UserEditService], // scoped to this component subtree\n})\nexport class UserEditComponent {}\n```\n\n### `InjectionToken`\n\n```typescript\nexport const CONFIG = new InjectionToken<AppConfig>('APP_CONFIG');\n\n// In providers:\n{ provide: CONFIG, useValue: appConfig }\n{ provide: CONFIG, useFactory: () => loadConfig(), deps: [] }\n\n// Consume:\nprivate config = inject(CONFIG);\n```\n\n### `viewProviders` vs `providers`\n\n- `providers`: Available to the component and all its content children\n- `viewProviders`: Available only to the component's own view (not projected content)\n\n## HTTP Interceptors\n\nUse functional interceptors (v15+) for auth, error handling, and retries:\n\n```typescript\nexport const authInterceptor: HttpInterceptorFn = (req, next) => {\n const token = inject(AuthService).token();\n if (!token) return next(req);\n return next(req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }));\n};\n```\n\nRegister in `app.config.ts`:\n\n```typescript\nprovideHttpClient(withInterceptors([authInterceptor, errorInterceptor]))\n```\n\n## RxJS Operators\n\n- `switchMap` — search, navigation (cancels previous)\n- `mergeMap` — independent parallel requests\n- `exhaustMap` — form submissions (ignores until complete)\n- Always handle errors with `catchError` — never let streams die silently\n\n```typescript\nsearch$ = this.query$.pipe(\n debounceTime(300),\n distinctUntilChanged(),\n switchMap(q => this.service.search(q).pipe(catchError(() => of([])))),\n);\n```\n\n## Forms\n\nMatch the project's existing form strategy. For new v21+ apps, prefer signal forms.\n\n```typescript\n// Reactive Forms — standard for complex forms\nexport class UserFormComponent {\n private fb = inject(FormBuilder);\n\n form = this.fb.group({\n name: ['', Validators.required],\n email: ['', [Validators.required, Validators.email]],\n });\n}\n```\n\n## Rendering Strategies\n\n- **CSR** (default): Standard SPA\n- **SSR + Hydration**: `ng add @angular/ssr` — improves FCP and SEO\n- **SSG (Prerendering)**: Static pages at build time for content-heavy routes\n\nWhen using SSR, avoid `window`, `document`, `localStorage` directly — use `isPlatformBrowser` or `DOCUMENT` token.\n\n## Accessibility\n\nUse Angular CDK for headless, accessible components (Accordion, Listbox, Combobox, Menu, Tabs, Toolbar, Tree, Grid). Style ARIA attributes rather than managing them manually:\n\n```css\n[aria-selected=\"true\"] { background: var(--color-selected); }\n```\n\n## Skill Reference\n\nSee skill: `angular-developer` for deep guidance on signals, forms, routing, DI, SSR, and accessibility patterns.\n\n### angular/security\n\n# Angular Security\n\n> This file extends [common/security.md](../common/security.md) with Angular specific content.\n\n## XSS Prevention\n\nAngular auto-sanitizes bound values. Never bypass the sanitizer on user-controlled input.\n\n```typescript\n// WRONG: Bypasses sanitization — XSS risk\nthis.safeHtml = this.sanitizer.bypassSecurityTrustHtml(userInput);\n\n// CORRECT: Sanitize explicitly before trusting\nthis.safeHtml = this.sanitizer.sanitize(SecurityContext.HTML, userInput);\n```\n\n- Never use `bypassSecurityTrust*` methods without a documented, reviewed reason\n- Avoid `[innerHTML]` with untrusted content — use `innerText` or a sanitizing pipe\n- Never bind `[href]` to user input — Angular does not block `javascript:` URLs in all contexts\n- Never construct template strings from user data\n\n## HTTP Security\n\nUse `HttpClient` exclusively — never raw `fetch()` or `XHR` unless no alternative exists.\n\n```typescript\n// WRONG: Bypasses interceptors (auth headers, error handling, logging)\nconst res = await fetch('/api/users');\n\n// CORRECT\nusers$ = this.http.get<User[]>('/api/users');\n```\n\n- Attach auth tokens via interceptors — never hardcode in individual service calls\n- Type and validate API responses — treat external data as `unknown` at the boundary\n- Never log HTTP responses that may contain tokens, PII, or credentials\n\n## Secret Management\n\n```typescript\n// WRONG: Hardcoded secret in source\nconst apiKey = 'sk-live-xxxx';\n\n// CORRECT: Injected via environment\nimport { environment } from '../environments/environment';\nconst apiKey = environment.apiKey;\n```\n\n- Treat `environment.ts` as a config shape — never store real secrets in source-controlled environment files\n- Inject production secrets via CI/CD (environment variables, secret managers)\n\n## Route Guards\n\nEvery authenticated or role-restricted route must have a guard. Never rely on hiding UI elements alone.\n\n```typescript\n{\n path: 'admin',\n canMatch: [authGuard, roleGuard('admin')],\n loadChildren: () => import('./admin/admin.routes'),\n}\n```\n\nUse `canMatch` for sensitive routes — it prevents the route module from loading at all for unauthorized users.\n\n## SSR Security\n\nWhen using Angular SSR:\n\n- Never expose server-side environment variables to the client via `TransferState` unless they are intentionally public\n- Sanitize all inputs before server-side rendering — DOM-based XSS can occur server-side too\n- Avoid `window`, `document`, `localStorage` on the server — gate with `isPlatformBrowser` or inject via `DOCUMENT` token\n\n## Content Security Policy\n\nConfigure CSP headers server-side. Avoid `unsafe-inline` in `script-src`. When using SSR with inline scripts, use nonces via Angular's CSP support.\n\n## Agent Support\n\n- Use **security-reviewer** skill for comprehensive security audits\n\n### angular/testing\n\n# Angular Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Angular specific content.\n\n## Test Runner\n\nUse the test runner configured by the project. Check `angular.json` and `package.json`; Angular projects commonly use Vitest, Jest, or Jasmine + Karma.\n\n```bash\nng test # watch mode\nng test --no-watch # CI mode\n```\n\n## TestBed Setup\n\nFor standalone components, import the component directly. Call `compileComponents()` for components with external templates.\n\n```typescript\ndescribe('UserCardComponent', () => {\n let fixture: ComponentFixture<UserCardComponent>;\n\n beforeEach(async () => {\n await TestBed.configureTestingModule({\n imports: [UserCardComponent],\n }).compileComponents();\n\n fixture = TestBed.createComponent(UserCardComponent);\n });\n});\n```\n\n## Signal Inputs\n\nSet signal-based inputs via `fixture.componentRef.setInput()`:\n\n```typescript\nfixture.componentRef.setInput('user', mockUser);\nfixture.detectChanges();\n```\n\n## Component Harnesses\n\nPrefer Angular CDK component harnesses over direct DOM queries for UI interaction. Harnesses are more resilient to markup changes.\n\n```typescript\nimport { HarnessLoader } from '@angular/cdk/testing';\nimport { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';\nimport { MatButtonHarness } from '@angular/material/button/testing';\n\nlet loader: HarnessLoader;\n\nbeforeEach(() => {\n loader = TestbedHarnessEnvironment.loader(fixture);\n});\n\nit('triggers save on button click', async () => {\n const button = await loader.getHarness(MatButtonHarness.with({ text: 'Save' }));\n await button.click();\n expect(saveSpy).toHaveBeenCalled();\n});\n```\n\n## Router Testing\n\nUse `RouterTestingHarness` for components that depend on the router:\n\n```typescript\nimport { RouterTestingHarness } from '@angular/router/testing';\n\nit('renders user on navigation', async () => {\n const harness = await RouterTestingHarness.create();\n const component = await harness.navigateByUrl('/users/1', UserDetailComponent);\n expect(component.userId()).toBe('1');\n});\n```\n\n## Async Testing\n\nUse `fakeAsync` + `tick` for controlled async. Use `waitForAsync` for real async with `fixture.whenStable()`.\n\n```typescript\nit('loads user after delay', fakeAsync(() => {\n const service = TestBed.inject(UserService);\n vi.spyOn(service, 'getUser').mockReturnValue(of(mockUser));\n\n fixture.detectChanges();\n tick();\n fixture.detectChanges();\n\n expect(fixture.nativeElement.querySelector('.name').textContent).toBe(mockUser.name);\n}));\n```\n\n## HTTP Testing\n\n```typescript\nimport { provideHttpClientTesting } from '@angular/common/http/testing';\nimport { HttpTestingController } from '@angular/common/http/testing';\n\nbeforeEach(() => {\n TestBed.configureTestingModule({\n providers: [provideHttpClient(), provideHttpClientTesting()],\n });\n httpMock = TestBed.inject(HttpTestingController);\n});\n\nafterEach(() => httpMock.verify());\n```\n\n## Service Testing\n\nInject services directly without a component fixture:\n\n```typescript\ndescribe('UserService', () => {\n let service: UserService;\n\n beforeEach(() => {\n TestBed.configureTestingModule({\n providers: [provideHttpClient(), provideHttpClientTesting()],\n });\n service = TestBed.inject(UserService);\n });\n});\n```\n\n## What to Test\n\n- **Services**: All public methods, error paths, HTTP interactions\n- **Components**: Input/output bindings, rendered output for key states, user interactions via harnesses\n- **Pipes**: Pure transformation — plain unit tests, no TestBed needed\n- **Guards/Resolvers**: Return values for allowed and denied states using `RouterTestingHarness`\n\n## E2E Testing\n\nUse the project's configured E2E framework, such as Cypress or Playwright, for critical user flows.\n\n```typescript\ndescribe('Login flow', () => {\n it('redirects to dashboard on valid credentials', () => {\n cy.visit('/login');\n cy.get('[data-cy=email]').type('user@example.com');\n cy.get('[data-cy=password]').type('password123');\n cy.get('[data-cy=submit]').click();\n cy.url().should('include', '/dashboard');\n });\n});\n```\n\n- Add `data-cy` attributes to interactive elements for stable selectors\n- Do not rely on CSS classes or text content for selectors in E2E tests\n\n## Coverage\n\nTarget ≥80% for services and pipes. Components: test behaviour, not implementation details.\n\n## Skill Reference\n\nSee skill: `angular-developer` for comprehensive testing patterns, harness usage, and async best practices.\n\n### arkts/coding-style\n\n# HarmonyOS / ArkTS Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with HarmonyOS and ArkTS-specific content.\n\n## ArkTS Language Constraints\n\nArkTS is a strict, statically-typed subset of TypeScript. Violating these constraints causes **compilation failures**.\n\n### Type System\n\n- No `any` or `unknown` types - always use explicit types\n- No index access types - use type names directly\n- No conditional type aliases or `infer` keyword\n- No intersection types - use inheritance\n- No mapped types - use classes and regular idioms\n- No `typeof` for type annotations - use explicit type declarations\n- No `as const` assertions - use explicit type annotations\n- No structural typing - use inheritance, interfaces, or type aliases\n- No TypeScript utility types except `Partial`, `Required`, `Readonly`, `Record`\n- For `Record<K, V>`, index expression type is `V | undefined`\n- Omit type annotations in `catch` clauses (ArkTS does not support `any`/`unknown`)\n\n### Functions & Classes\n\n- No function expressions - use arrow functions\n- No nested functions - use lambdas\n- No generator functions - use `async`/`await` for multitasking\n- No `Function.apply`, `Function.call`, `Function.bind` - follow traditional OOP for `this`\n- No constructor type expressions - use lambdas\n- No constructor signatures in interfaces or object types - use methods or classes\n- No declaring class fields in constructors - declare in class body\n- No `this` in standalone functions or static methods - only in instance methods\n- No `new.target`\n- No definite assignment assertions (`let v!: T`) - use initialized declarations\n- No class literals - introduce named class types\n- No using classes as objects (assigning to variables) - class declarations introduce types, not values\n- Only one static block per class - merge all static statements\n\n### Object & Property Access\n\n- No dynamic field declaration or `obj[\"field\"]` access - use `obj.field` syntax\n- No `delete` operator - use nullable type with `null` to mark absence\n- No prototype assignment - use classes and interfaces\n- No `in` operator - use `instanceof`\n- No reassigning object methods - use wrapper functions or inheritance\n- No `Symbol()` API (except `Symbol.iterator`)\n- No `globalThis` or global scope - use explicit module exports/imports\n- No namespaces as objects - use classes or modules\n- No statements inside namespaces - use functions\n\n### Destructuring & Spread\n\n- No destructuring assignments or variable declarations - use intermediate objects and field-by-field access\n- No destructuring parameter declarations - pass parameters directly, assign local names manually\n- Spread operator only for expanding arrays (or array-derived classes) into rest parameters or array literals\n\n### Modules & Imports\n\n- No `require()` - use regular `import` syntax\n- No `export = ...` - use normal export/import\n- No import assertions - imports are compile-time in ArkTS\n- No UMD modules\n- No wildcards in module names\n- All `import` statements must appear before all other statements\n- TypeScript codebases must not depend on ArkTS codebases via import (reverse is supported)\n\n### Other Restrictions\n\n- No `var` - use `let`\n- No `for...in` loops - use regular `for` loops for arrays\n- No `with` statements\n- No JSX expressions\n- No `#` private identifiers - use `private` keyword\n- No declaration merging (classes, interfaces, enums) - keep definitions compact\n- No index signatures - use arrays\n- Comma operator only in `for` loops\n- Unary operators `+`, `-`, `~` only for numeric types (no implicit string conversion)\n- Enum members: only same-type compile-time expressions for explicit initializers\n- Function return type inference is limited - specify return types explicitly when calling functions with omitted return types\n\n### Object Literals\n\n- Supported only when compiler can infer the corresponding class or interface\n- NOT supported for: `any`/`Object`/`object` types, classes/interfaces with methods, classes with parameterized constructors, classes with `readonly` fields\n\n## Naming Conventions\n\n- Variables / functions: `camelCase` (e.g., `getUserInfo`, `goodsList`)\n- Classes / interfaces: `PascalCase` (e.g., `UserViewModel`, `IGoodsModel`)\n- Constants: `UPPER_SNAKE_CASE` (e.g., `MAX_PAGE_SIZE`, `COLOR_PRIMARY`)\n- File names: `PascalCase` for components (e.g., `HomePage.ets`), `camelCase` for utilities\n\n## Formatting\n\n- Prefer double quotes for strings\n- Semicolons at end of statements\n- Never use `var` - prefer `const`, then `let`\n- All methods, parameters, return values must have complete type annotations\n\n## File Organization\n\n- Component files (`.ets`): one `@ComponentV2` per file\n- ViewModel files: one ViewModel class per file\n- Model files: related data models may share a file\n- Keep files under 400 lines; extract helpers for files approaching 800 lines\n\n## Comments\n\n- File header: `@file` (file purpose) + `@author` (developer), if the project already uses file headers\n- Public methods: JSDoc with `@param`, `@returns`; add `@example` for complex methods\n- Match the project's existing documentation language; use English unless the repository has already standardized on Chinese comments\n\n## Error Handling\n\n```typescript\n// Use try/catch with proper error handling\ntry {\n const result = await riskyOperation()\n return result\n} catch (error) {\n hilog.error(0x0000, 'TAG', 'Operation failed: %{public}s', error)\n throw new Error('User-friendly error message')\n}\n```\n\n## Immutability\n\nFollow the common immutability principles - create new objects instead of mutating:\n\n```typescript\n// BAD: mutation\nfunction updateUser(user: UserModel, name: string): UserModel {\n user.name = name // direct mutation\n return user\n}\n\n// GOOD: immutable - create new instance\nfunction updateUser(user: UserModel, name: string): UserModel {\n const updated = new UserModel()\n updated.id = user.id\n updated.name = name\n updated.email = user.email\n return updated\n}\n```\n\n### arkts/hooks\n\n# HarmonyOS / ArkTS Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with HarmonyOS-specific build and validation hooks.\n\n## Build Commands\n\n### HAP Package Build\n\n```bash\n# Build HAP package (global hvigor environment)\nhvigorw assembleHap -p product=default\n\n# Build with specific module\nhvigorw assembleHap -p module=entry -p product=default\n\n# Clean build\nhvigorw clean\n```\n\n### DevEco Studio CLI\n\n```bash\n# Check project structure\nhvigorw --version\n\n# Install dependencies\nohpm install\n\n# Update dependencies\nohpm update\n```\n\n## Recommended PostToolUse Hooks\n\n### After Editing .ets/.ts Files\n\nRun hvigor build to check for ArkTS compilation errors:\n\n```json\n{\n \"type\": \"PostToolUse\",\n \"matcher\": {\n \"tool\": [\"Edit\", \"Write\"],\n \"filePath\": [\"**/*.ets\", \"**/*.ts\"]\n },\n \"hooks\": [\n {\n \"command\": \"hvigorw assembleHap -p product=default 2>&1 | tail -20\",\n \"async\": true,\n \"timeout\": 60000\n }\n ]\n}\n```\n\n### After Editing module.json5\n\nValidate permission and ability declarations:\n\n```json\n{\n \"type\": \"PostToolUse\",\n \"matcher\": {\n \"tool\": \"Edit\",\n \"filePath\": \"**/module.json5\"\n },\n \"hooks\": [\n {\n \"command\": \"echo '[HarmonyOS] module.json5 modified - verify permissions and abilities'\",\n \"async\": false\n }\n ]\n}\n```\n\n### After Editing oh-package.json5\n\nReinstall dependencies:\n\n```json\n{\n \"type\": \"PostToolUse\",\n \"matcher\": {\n \"tool\": \"Edit\",\n \"filePath\": \"**/oh-package.json5\"\n },\n \"hooks\": [\n {\n \"command\": \"ohpm install 2>&1 | tail -10\",\n \"async\": true,\n \"timeout\": 30000\n }\n ]\n}\n```\n\n## PreToolUse Hooks\n\n### V1 Decorator Guard\n\nWarn when code contains V1 state management decorators:\n\n```json\n{\n \"type\": \"PreToolUse\",\n \"matcher\": {\n \"tool\": [\"Write\", \"Edit\"],\n \"filePath\": \"**/*.ets\"\n },\n \"hooks\": [\n {\n \"command\": \"echo '[HarmonyOS] Reminder: Use @ComponentV2 / @Local / @Param - V1 decorators (@State, @Prop, @Link) are prohibited'\"\n }\n ]\n}\n```\n\n## Validation Checklist\n\nAfter each implementation cycle, verify:\n\n- [ ] `hvigorw assembleHap` completes without errors\n- [ ] No V1 decorators in new or modified `.ets` files\n- [ ] No `@ohos.router` imports in new or modified files\n- [ ] All API permissions declared in `module.json5`\n- [ ] All dependencies listed in `oh-package.json5`\n- [ ] Resource strings added to all i18n directories\n- [ ] Dark theme colors provided for new color resources\n\n### arkts/patterns\n\n# HarmonyOS / ArkTS Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with HarmonyOS and ArkTS-specific patterns.\n\n## State Management: V2 Only\n\n**MUST use** ArkUI State Management V2. V1 decorators are deprecated and must not be used.\n\n### V2 Decorators\n\n| Decorator | Purpose |\n|-----------|---------|\n| `@ComponentV2` | Marks a struct as a V2 component |\n| `@Local` | Local state within a component |\n| `@Param` | Props received from parent (read-only) |\n| `@Event` | Callback events from child to parent |\n| `@Provider` | Provides state to descendant components |\n| `@Consumer` | Consumes state from ancestor `@Provider` |\n| `@Monitor` | Watches for state changes (replaces V1 `@Watch`) |\n| `@Computed` | Derived/computed values |\n| `@ObservedV2` | Makes a class observable for V2 state management |\n| `@Trace` | Marks observable properties in `@ObservedV2` classes |\n\n### Prohibited V1 Decorators\n\nNever use: `@State`, `@Prop`, `@Link`, `@ObjectLink`, `@Observed`, `@Provide`, `@Consume`, `@Watch`, `@Component` (use `@ComponentV2` instead).\n\n### V2 Component Example\n\n```typescript\n@ObservedV2\nclass UserModel {\n @Trace name: string = ''\n @Trace age: number = 0\n}\n\n@ComponentV2\nstruct UserCard {\n @Param user: UserModel = new UserModel()\n @Event onDelete: () => void = () => {}\n\n build() {\n Column() {\n Text(this.user.name)\n .fontSize($r('app.float.font_size_title'))\n Text(`${this.user.age}`)\n .fontSize($r('app.float.font_size_body'))\n Button($r('app.string.delete'))\n .onClick(() => this.onDelete())\n }\n }\n}\n```\n\n### State Synchronization\n\n```typescript\n@ComponentV2\nstruct ParentPage {\n @Provider('userState') userModel: UserModel = new UserModel()\n\n build() {\n Column() {\n ChildComponent() // automatically receives @Consumer('userState')\n }\n }\n}\n\n@ComponentV2\nstruct ChildComponent {\n @Consumer('userState') userModel: UserModel = new UserModel()\n\n build() {\n Text(this.userModel.name)\n }\n}\n```\n\n## Routing: Navigation Only\n\n**MUST use** `Navigation` component with `NavPathStack`. Never use `@ohos.router`.\n\n### Navigation Setup\n\n```typescript\n@ComponentV2\nstruct MainPage {\n @Local navPathStack: NavPathStack = new NavPathStack()\n\n build() {\n Navigation(this.navPathStack) {\n // Home content\n }\n .navDestination(this.routerMap)\n }\n\n @Builder\n routerMap(name: string, param: ESObject) {\n if (name === 'detail') {\n DetailPage()\n } else if (name === 'settings') {\n SettingsPage()\n }\n }\n}\n```\n\n### Page Navigation\n\n```typescript\n// Push a new page\nthis.navPathStack.pushPath({ name: 'detail', param: { id: '123' } })\n\n// Replace current page\nthis.navPathStack.replacePath({ name: 'settings' })\n\n// Pop back\nthis.navPathStack.pop()\n\n// Pop to root\nthis.navPathStack.clear()\n```\n\n### NavDestination Sub-page\n\n```typescript\n@ComponentV2\nstruct DetailPage {\n build() {\n NavDestination() {\n Column() {\n Text($r('app.string.detail_title'))\n }\n }\n .title($r('app.string.detail_nav_title'))\n }\n}\n```\n\n## Architecture Pattern: MVVM\n\nRecommended architecture for HarmonyOS applications:\n\n```\nfeature/\n |-- model/ # Data models (@ObservedV2 classes)\n |-- viewmodel/ # Business logic (ViewModel classes)\n |-- view/ # UI components (@ComponentV2 structs)\n |-- service/ # API calls, data access\n```\n\n- **View**: Only rendering logic, no business logic in `build()`\n- **ViewModel**: All business logic encapsulated here\n- **Model**: Pure data classes with `@ObservedV2` and `@Trace`\n- **Service**: Network requests, database operations, file I/O\n\n## ArkUI Animation Patterns\n\n### State-Driven Animation\n\n```typescript\n@ComponentV2\nstruct AnimatedCard {\n @Local isExpanded: boolean = false\n @Local cardScale: number = 0.8\n\n build() {\n Column() {\n // Content\n }\n .scale({ x: this.cardScale, y: this.cardScale })\n .animation({ duration: 300, curve: Curve.EaseInOut })\n .onClick(() => {\n this.isExpanded = !this.isExpanded\n this.cardScale = this.isExpanded ? 1.0 : 0.8\n })\n }\n}\n```\n\n### Animation Rules\n\n- Prefer native HarmonyOS animation APIs and advanced templates\n- Use declarative UI with state-driven animations (change state variables to trigger animations)\n- Set `renderGroup(true)` for complex sub-component animations to reduce render batches\n- **NEVER** frequently change `width`, `height`, `padding`, `margin` during animations - severe performance impact\n- Use `animateTo` for explicit animation control\n- Prefer `transform` (translate, scale, rotate) and `opacity` for performant animations\n\n## Performance Patterns\n\n### LazyForEach for Large Lists\n\n```typescript\n@ComponentV2\nstruct LargeList {\n @Local dataSource: MyDataSource = new MyDataSource()\n\n build() {\n List() {\n LazyForEach(this.dataSource, (item: ItemModel) => {\n ListItem() {\n ItemComponent({ item: item })\n }\n }, (item: ItemModel) => item.id)\n }\n }\n}\n```\n\n### Component Reuse\n\n- Extract reusable components into separate files\n- Use `@Builder` for lightweight UI fragments within a component\n- Use `@Param` for configurable components\n\n## Resource References\n\nAlways define UI constants as resources and reference via `$r()`:\n\n```typescript\n// BAD: hardcoded values\nText('Hello')\n .fontSize(16)\n .fontColor('#333333')\n\n// GOOD: resource references\nText($r('app.string.greeting'))\n .fontSize($r('app.float.font_size_body'))\n .fontColor($r('app.color.text_primary'))\n```\n\n### arkts/security\n\n# HarmonyOS / ArkTS Security\n\n> This file extends [common/security.md](../common/security.md) with HarmonyOS-specific security practices.\n\n## Permission Management\n\n### Declare Permissions in module.json5\n\nAll system API calls requiring permissions must be declared:\n\n```json5\n{\n \"module\": {\n \"requestPermissions\": [\n {\n \"name\": \"ohos.permission.INTERNET\",\n \"reason\": \"$string:internet_permission_reason\",\n \"usedScene\": {\n \"abilities\": [\"EntryAbility\"],\n \"when\": \"always\"\n }\n }\n ]\n }\n}\n```\n\n### Permission Checklist\n\nBefore calling system APIs, verify:\n\n- [ ] Permission declared in `module.json5`\n- [ ] Permission reason string defined in resources (for user-facing permissions)\n- [ ] Runtime permission request implemented for sensitive permissions (camera, location, etc.)\n- [ ] Permission check before API call with graceful fallback on denial\n\n### Runtime Permission Request\n\n```typescript\nimport { abilityAccessCtrl, bundleManager, Permissions } from '@kit.AbilityKit';\n\nasync function checkAndRequestPermission(permission: Permissions): Promise<boolean> {\n const atManager = abilityAccessCtrl.createAtManager();\n const bundleInfo = await bundleManager.getBundleInfoForSelf(\n bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION\n );\n const tokenId = bundleInfo.appInfo.accessTokenId;\n const grantStatus = await atManager.checkAccessToken(tokenId, permission);\n\n if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {\n return true;\n }\n\n const result = await atManager.requestPermissionsFromUser(getContext(), [permission]);\n return result.authResults[0] === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;\n}\n```\n\n## Secret Management\n\n- **NEVER** hardcode API keys, tokens, or passwords in `.ets`/`.ts` source files\n- Use HarmonyOS Preferences API for non-sensitive configuration\n- Use HarmonyOS Keystore for sensitive credentials\n- Environment-specific configs should be managed via build profiles\n\n```typescript\n// BAD: hardcoded secret\nconst API_KEY: string = 'sk-xxxxxxxxxxxx';\n\n// GOOD: from build profile config (non-sensitive)\nimport { BuildProfile } from 'BuildProfile';\nconst endpoint = BuildProfile.API_ENDPOINT;\n\n// GOOD: use HUKS to encrypt/decrypt data without exposing key material\nimport { huks } from '@kit.UniversalKeystoreKit';\nasync function decryptWithKeystore(alias: string, nonce: Uint8Array, aad: Uint8Array, cipherData: Uint8Array): Promise<Uint8Array> {\n const options: huks.HuksOptions = {\n properties: [\n { tag: huks.HuksTag.HUKS_TAG_ALGORITHM, value: huks.HuksKeyAlg.HUKS_ALG_AES },\n { tag: huks.HuksTag.HUKS_TAG_PURPOSE, value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT },\n { tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, value: huks.HuksCipherMode.HUKS_MODE_GCM },\n { tag: huks.HuksTag.HUKS_TAG_PADDING, value: huks.HuksKeyPadding.HUKS_PADDING_NONE },\n { tag: huks.HuksTag.HUKS_TAG_NONCE, value: nonce },\n { tag: huks.HuksTag.HUKS_TAG_ASSOCIATED_DATA, value: aad }\n ],\n inData: cipherData\n };\n const handle = await huks.initSession(alias, options);\n const result = await huks.finishSession(handle.handle, options);\n return result.outData;\n}\n```\n\n## Input Validation\n\n- Validate all user input before processing\n- Sanitize data before displaying in UI to prevent injection\n- Validate deep link parameters before navigation\n\n```typescript\n// Validate before navigation\nfunction handleDeepLink(uri: string): void {\n const allowedPaths: string[] = ['detail', 'settings', 'profile'];\n const parsed = new URL(uri);\n const path = parsed.pathname.replace('/', '');\n\n if (!allowedPaths.includes(path)) {\n hilog.warn(0x0000, 'DeepLink', 'Invalid deep link path: %{public}s', path);\n return;\n }\n\n navPathStack.pushPath({ name: path });\n}\n```\n\n## Network Security\n\n- Always use HTTPS for network requests\n- Validate server certificates\n- Implement request timeout and retry policies\n- Never log sensitive data (tokens, user credentials) in network request/response logs\n\n## Data Storage Security\n\n- Use encrypted preferences for sensitive local data\n- Clear sensitive data from memory when no longer needed\n- Implement proper data lifecycle management\n- Consider data classification (public, internal, confidential) when choosing storage mechanisms\n\n## Dependency Security\n\n- Only use dependencies from trusted sources (official ohpm registry)\n- Verify dependency versions in `oh-package.json5`\n- Regularly check for known vulnerabilities in third-party libraries\n- Pin dependency versions to avoid unexpected updates\n\n### arkts/testing\n\n# HarmonyOS / ArkTS Testing\n\n> This file extends [common/testing.md](../common/testing.md) with HarmonyOS-specific testing practices.\n\n## Test Framework\n\nHarmonyOS uses the built-in test framework with `@ohos.test` capabilities:\n\n- **Unit tests**: Located in `src/ohosTest/ets/test/`\n- **UI tests**: Use `@ohos.UiTest` for component testing\n- **Instrument tests**: Run on device/emulator\n\n## Test Directory Structure\n\n```\nmodule/\n |-- src/\n | |-- main/ets/ # Production code\n | |-- ohosTest/ets/ # Test code\n | |-- test/\n | | |-- Ability.test.ets\n | | |-- List.test.ets\n | |-- TestAbility.ets\n | |-- TestRunner.ets\n```\n\n## Running Tests\n\n```bash\n# Run all tests for a module\nhvigorw testHap -p product=default\n\n# Run tests on connected device\nhdc shell aa test -b com.example.app -m entry_test -s unittest /ets/TestRunner/OpenHarmonyTestRunner\n```\n\n## Unit Test Example\n\n```typescript\nimport { describe, it, expect } from '@ohos/hypium';\n\nexport default function UserViewModelTest() {\n describe('UserViewModel', () => {\n it('should_initialize_with_empty_state', 0, () => {\n const vm = new UserViewModel();\n expect(vm.userName).assertEqual('');\n expect(vm.isLoading).assertFalse();\n });\n\n it('should_update_user_name', 0, () => {\n const vm = new UserViewModel();\n vm.updateUserName('Alice');\n expect(vm.userName).assertEqual('Alice');\n });\n\n it('should_handle_empty_input', 0, () => {\n const vm = new UserViewModel();\n vm.updateUserName('');\n expect(vm.userName).assertEqual('');\n expect(vm.hasError).assertFalse();\n });\n });\n}\n```\n\n## UI Test Example\n\n```typescript\nimport { describe, it, expect } from '@ohos/hypium';\nimport { Driver, ON } from '@ohos.UiTest';\n\nexport default function HomePageUITest() {\n describe('HomePage_UI', () => {\n it('should_display_title', 0, async () => {\n const driver = Driver.create();\n await driver.delayMs(1000);\n\n const title = await driver.findComponent(ON.text('Home'));\n expect(title !== null).assertTrue();\n });\n\n it('should_navigate_to_detail_on_click', 0, async () => {\n const driver = Driver.create();\n const button = await driver.findComponent(ON.id('detailButton'));\n await button.click();\n await driver.delayMs(500);\n\n const detailTitle = await driver.findComponent(ON.text('Detail'));\n expect(detailTitle !== null).assertTrue();\n });\n });\n}\n```\n\n## TDD Workflow for HarmonyOS\n\nFollow the standard TDD cycle adapted for HarmonyOS:\n\n1. **RED**: Write a failing test in `ohosTest/ets/test/`\n2. **GREEN**: Implement minimal code in `main/ets/` to pass\n3. **REFACTOR**: Clean up while keeping tests green\n4. **BUILD**: Run `hvigorw assembleHap` to verify compilation\n5. **VERIFY**: Run tests on device/emulator\n\n## Test Coverage Requirements\n\n- Minimum 80% coverage for all critical application code (ViewModels, services, utilities)\n- **Unit tests**: All utility functions, ViewModel logic, data models\n- **Integration tests**: API calls, database operations, cross-module interactions\n- **E2E / UI tests**: Critical user flows (login, navigation, data submission)\n- Test edge cases: empty data, network errors, permission denials\n\n## Testing Best Practices\n\n- Keep tests independent - no shared mutable state between tests\n- Mock network calls and system APIs in unit tests\n- Use meaningful test names: `should_[expected_behavior]_when_[condition]`\n- Test V2 state management reactivity: verify `@Trace` properties trigger UI updates\n- Test Navigation flows: verify `NavPathStack` push/pop/replace operations\n- Avoid testing framework internals - focus on business logic and user-visible behavior\n\n### common/agents\n\n# Agent Orchestration\n\n## Available Agents\n\nLocated in `~/.claude/agents/`:\n\n| Agent | Purpose | When to Use |\n|-------|---------|-------------|\n| planner | Implementation planning | Complex features, refactoring |\n| architect | System design | Architectural decisions |\n| tdd-guide | Test-driven development | New features, bug fixes |\n| code-reviewer | Code review | After writing code |\n| security-reviewer | Security analysis | Before commits |\n| build-error-resolver | Fix build errors | When build fails |\n| e2e-runner | E2E testing | Critical user flows |\n| refactor-cleaner | Dead code cleanup | Code maintenance |\n| doc-updater | Documentation | Updating docs |\n| rust-reviewer | Rust code review | Rust projects |\n| harmonyos-app-resolver | HarmonyOS app development | HarmonyOS/ArkTS projects |\n\n## Immediate Agent Usage\n\nNo user prompt needed:\n1. Complex feature requests - Use **planner** agent\n2. Code just written/modified - Use **code-reviewer** agent\n3. Bug fix or new feature - Use **tdd-guide** agent\n4. Architectural decision - Use **architect** agent\n\n## Parallel Task Execution\n\nALWAYS use parallel Task execution for independent operations:\n\n```markdown\n# GOOD: Parallel execution\nLaunch 3 agents in parallel:\n1. Agent 1: Security analysis of auth module\n2. Agent 2: Performance review of cache system\n3. Agent 3: Type checking of utilities\n\n# BAD: Sequential when unnecessary\nFirst agent 1, then agent 2, then agent 3\n```\n\n## Multi-Perspective Analysis\n\nFor complex problems, use split role sub-agents:\n- Factual reviewer\n- Senior engineer\n- Security expert\n- Consistency reviewer\n- Redundancy checker\n\n### common/code-review\n\n# Code Review Standards\n\n## Purpose\n\nCode review ensures quality, security, and maintainability before code is merged. This rule defines when and how to conduct code reviews.\n\n## When to Review\n\n**MANDATORY review triggers:**\n\n- After writing or modifying code\n- Before any commit to shared branches\n- When security-sensitive code is changed (auth, payments, user data)\n- When architectural changes are made\n- Before merging pull requests\n\n**Pre-Review Requirements:**\n\nBefore requesting review, ensure:\n\n- All automated checks (CI/CD) are passing\n- Merge conflicts are resolved\n- Branch is up to date with target branch\n\n## Review Checklist\n\nBefore marking code complete:\n\n- [ ] Code is readable and well-named\n- [ ] Functions are focused (<50 lines)\n- [ ] Files are cohesive (<800 lines)\n- [ ] No deep nesting (>4 levels)\n- [ ] Errors are handled explicitly\n- [ ] No hardcoded secrets or credentials\n- [ ] No console.log or debug statements\n- [ ] Tests exist for new functionality\n- [ ] Test coverage meets 80% minimum\n\n## Security Review Triggers\n\n**STOP and use security-reviewer agent when:**\n\n- Authentication or authorization code\n- User input handling\n- Database queries\n- File system operations\n- External API calls\n- Cryptographic operations\n- Payment or financial code\n\n## Review Severity Levels\n\n| Level | Meaning | Action |\n|-------|---------|--------|\n| CRITICAL | Security vulnerability or data loss risk | **BLOCK** - Must fix before merge |\n| HIGH | Bug or significant quality issue | **WARN** - Should fix before merge |\n| MEDIUM | Maintainability concern | **INFO** - Consider fixing |\n| LOW | Style or minor suggestion | **NOTE** - Optional |\n\n## Agent Usage\n\nUse these agents for code review:\n\n| Agent | Purpose |\n|-------|---------|\n| **code-reviewer** | General code quality, patterns, best practices |\n| **security-reviewer** | Security vulnerabilities, OWASP Top 10 |\n| **typescript-reviewer** | TypeScript/JavaScript specific issues |\n| **python-reviewer** | Python specific issues |\n| **go-reviewer** | Go specific issues |\n| **rust-reviewer** | Rust specific issues |\n\n## Review Workflow\n\n```\n1. Run git diff to understand changes\n2. Check security checklist first\n3. Review code quality checklist\n4. Run relevant tests\n5. Verify coverage >= 80%\n6. Use appropriate agent for detailed review\n```\n\n## Common Issues to Catch\n\n### Security\n\n- Hardcoded credentials (API keys, passwords, tokens)\n- SQL injection (string concatenation in queries)\n- XSS vulnerabilities (unescaped user input)\n- Path traversal (unsanitized file paths)\n- CSRF protection missing\n- Authentication bypasses\n\n### Code Quality\n\n- Large functions (>50 lines) - split into smaller\n- Large files (>800 lines) - extract modules\n- Deep nesting (>4 levels) - use early returns\n- Missing error handling - handle explicitly\n- Mutation patterns - prefer immutable operations\n- Missing tests - add test coverage\n\n### Performance\n\n- N+1 queries - use JOINs or batching\n- Missing pagination - add LIMIT to queries\n- Unbounded queries - add constraints\n- Missing caching - cache expensive operations\n\n## Approval Criteria\n\n- **Approve**: No CRITICAL or HIGH issues\n- **Warning**: Only HIGH issues (merge with caution)\n- **Block**: CRITICAL issues found\n\n## Integration with Other Rules\n\nThis rule works with:\n\n- [testing.md](testing.md) - Test coverage requirements\n- [security.md](security.md) - Security checklist\n- [git-workflow.md](git-workflow.md) - Commit standards\n- [agents.md](agents.md) - Agent delegation\n\n### common/coding-style\n\n# Coding Style\n\n## Immutability (CRITICAL)\n\nALWAYS create new objects, NEVER mutate existing ones:\n\n```\n// Pseudocode\nWRONG: modify(original, field, value) → changes original in-place\nCORRECT: update(original, field, value) → returns new copy with change\n```\n\nRationale: Immutable data prevents hidden side effects, makes debugging easier, and enables safe concurrency.\n\n## Core Principles\n\n### KISS (Keep It Simple)\n\n- Prefer the simplest solution that actually works\n- Avoid premature optimization\n- Optimize for clarity over cleverness\n\n### DRY (Don't Repeat Yourself)\n\n- Extract repeated logic into shared functions or utilities\n- Avoid copy-paste implementation drift\n- Introduce abstractions when repetition is real, not speculative\n\n### YAGNI (You Aren't Gonna Need It)\n\n- Do not build features or abstractions before they are needed\n- Avoid speculative generality\n- Start simple, then refactor when the pressure is real\n\n## File Organization\n\nMANY SMALL FILES > FEW LARGE FILES:\n- High cohesion, low coupling\n- 200-400 lines typical, 800 max\n- Extract utilities from large modules\n- Organize by feature/domain, not by type\n\n## Error Handling\n\nALWAYS handle errors comprehensively:\n- Handle errors explicitly at every level\n- Provide user-friendly error messages in UI-facing code\n- Log detailed error context on the server side\n- Never silently swallow errors\n\n## Input Validation\n\nALWAYS validate at system boundaries:\n- Validate all user input before processing\n- Use schema-based validation where available\n- Fail fast with clear error messages\n- Never trust external data (API responses, user input, file content)\n\n## Naming Conventions\n\n- Variables and functions: `camelCase` with descriptive names\n- Booleans: prefer `is`, `has`, `should`, or `can` prefixes\n- Interfaces, types, and components: `PascalCase`\n- Constants: `UPPER_SNAKE_CASE`\n- Custom hooks: `camelCase` with a `use` prefix\n\n## Code Smells to Avoid\n\n### Deep Nesting\n\nPrefer early returns over nested conditionals once the logic starts stacking.\n\n### Magic Numbers\n\nUse named constants for meaningful thresholds, delays, and limits.\n\n### Long Functions\n\nSplit large functions into focused pieces with clear responsibilities.\n\n## Code Quality Checklist\n\nBefore marking work complete:\n- [ ] Code is readable and well-named\n- [ ] Functions are small (<50 lines)\n- [ ] Files are focused (<800 lines)\n- [ ] No deep nesting (>4 levels)\n- [ ] Proper error handling\n- [ ] No hardcoded values (use constants or config)\n- [ ] No mutation (immutable patterns used)\n\n### common/development-workflow\n\n# Development Workflow\n\n> This file extends [common/git-workflow.md](./git-workflow.md) with the full feature development process that happens before git operations.\n\nThe Feature Implementation Workflow describes the development pipeline: research, planning, TDD, code review, and then committing to git.\n\n## Feature Implementation Workflow\n\n0. **Research & Reuse** _(mandatory before any new implementation)_\n - **GitHub code search first:** Run `gh search repos` and `gh search code` to find existing implementations, templates, and patterns before writing anything new.\n - **Library docs second:** Use Context7 or primary vendor docs to confirm API behavior, package usage, and version-specific details before implementing.\n - **Exa only when the first two are insufficient:** Use Exa for broader web research or discovery after GitHub search and primary docs.\n - **Check package registries:** Search npm, PyPI, crates.io, and other registries before writing utility code. Prefer battle-tested libraries over hand-rolled solutions.\n - **Search for adaptable implementations:** Look for open-source projects that solve 80%+ of the problem and can be forked, ported, or wrapped.\n - Prefer adopting or porting a proven approach over writing net-new code when it meets the requirement.\n\n1. **Plan First**\n - Use **planner** agent to create implementation plan\n - Generate planning docs before coding: PRD, architecture, system_design, tech_doc, task_list\n - Identify dependencies and risks\n - Break down into phases\n\n2. **TDD Approach**\n - Use **tdd-guide** agent\n - Write tests first (RED)\n - Implement to pass tests (GREEN)\n - Refactor (IMPROVE)\n - Verify 80%+ coverage\n\n3. **Code Review**\n - Use **code-reviewer** agent immediately after writing code\n - Address CRITICAL and HIGH issues\n - Fix MEDIUM issues when possible\n\n4. **Commit & Push**\n - Detailed commit messages\n - Follow conventional commits format\n - See [git-workflow.md](./git-workflow.md) for commit message format and PR process\n\n5. **Pre-Review Checks**\n - Verify all automated checks (CI/CD) are passing\n - Resolve any merge conflicts\n - Ensure branch is up to date with target branch\n - Only request review after these checks pass\n\n### common/git-workflow\n\n# Git Workflow\n\n## Commit Message Format\n```\n<type>: <description>\n\n<optional body>\n```\n\nTypes: feat, fix, refactor, docs, test, chore, perf, ci\n\nNote: To disable co-author attribution on commits, set `\"includeCoAuthoredBy\": false` in `~/.claude/settings.json` (Claude Code appends `Co-Authored-By` by default; ECC does not ship this setting).\n\n## Pull Request Workflow\n\nWhen creating PRs:\n1. Analyze full commit history (not just latest commit)\n2. Use `git diff [base-branch]...HEAD` to see all changes\n3. Draft comprehensive PR summary\n4. Include test plan with TODOs\n5. Push with `-u` flag if new branch\n\n> For the full development process (planning, TDD, code review) before git operations,\n> see [development-workflow.md](./development-workflow.md).\n\n### common/hooks\n\n# Hooks System\n\n## Hook Types\n\n- **PreToolUse**: Before tool execution (validation, parameter modification)\n- **PostToolUse**: After tool execution (auto-format, checks)\n- **Stop**: When session ends (final verification)\n\n## Auto-Accept Permissions\n\nUse with caution:\n- Enable for trusted, well-defined plans\n- Disable for exploratory work\n- Never use dangerously-skip-permissions flag\n- Configure `allowedTools` in `~/.claude.json` instead\n\n## TodoWrite Best Practices\n\nUse TodoWrite tool to:\n- Track progress on multi-step tasks\n- Verify understanding of instructions\n- Enable real-time steering\n- Show granular implementation steps\n\nTodo list reveals:\n- Out of order steps\n- Missing items\n- Extra unnecessary items\n- Wrong granularity\n- Misinterpreted requirements\n\n### common/patterns\n\n# Common Patterns\n\n## Skeleton Projects\n\nWhen implementing new functionality:\n1. Search for battle-tested skeleton projects\n2. Use parallel agents to evaluate options:\n - Security assessment\n - Extensibility analysis\n - Relevance scoring\n - Implementation planning\n3. Clone best match as foundation\n4. Iterate within proven structure\n\n## Design Patterns\n\n### Repository Pattern\n\nEncapsulate data access behind a consistent interface:\n- Define standard operations: findAll, findById, create, update, delete\n- Concrete implementations handle storage details (database, API, file, etc.)\n- Business logic depends on the abstract interface, not the storage mechanism\n- Enables easy swapping of data sources and simplifies testing with mocks\n\n### API Response Format\n\nUse a consistent envelope for all API responses:\n- Include a success/status indicator\n- Include the data payload (nullable on error)\n- Include an error message field (nullable on success)\n- Include metadata for paginated responses (total, page, limit)\n\n### common/performance\n\n# Performance Optimization\n\n## Model Selection Strategy\n\n**Haiku** (90% of Sonnet capability, 3x cost savings):\n- Lightweight agents with frequent invocation\n- Pair programming and code generation\n- Worker agents in multi-agent systems\n\n**Sonnet** (Best coding model):\n- Main development work\n- Orchestrating multi-agent workflows\n- Complex coding tasks\n\n**Opus** (Deepest reasoning):\n- Complex architectural decisions\n- Maximum reasoning requirements\n- Research and analysis tasks\n\n## Context Window Management\n\nAvoid last 20% of context window for:\n- Large-scale refactoring\n- Feature implementation spanning multiple files\n- Debugging complex interactions\n\nLower context sensitivity tasks:\n- Single-file edits\n- Independent utility creation\n- Documentation updates\n- Simple bug fixes\n\n## Extended Thinking + Plan Mode\n\nExtended thinking is enabled by default, reserving up to 31,999 tokens for internal reasoning.\n\nControl extended thinking via:\n- **Toggle**: Option+T (macOS) / Alt+T (Windows/Linux)\n- **Config**: Set `alwaysThinkingEnabled` in `~/.claude/settings.json`\n- **Budget cap**: `export MAX_THINKING_TOKENS=10000` (bash) or `$env:MAX_THINKING_TOKENS = \"10000\"` (PowerShell)\n- **Verbose mode**: Ctrl+O to see thinking output\n\nFor complex tasks requiring deep reasoning:\n1. Ensure extended thinking is enabled (on by default)\n2. Enable **Plan Mode** for structured approach\n3. Use multiple critique rounds for thorough analysis\n4. Use split role sub-agents for diverse perspectives\n\n## Build Troubleshooting\n\nIf build fails:\n1. Use **build-error-resolver** agent\n2. Analyze error messages\n3. Fix incrementally\n4. Verify after each fix\n\n### common/security\n\n# Security Guidelines\n\n## Mandatory Security Checks\n\nBefore ANY commit:\n- [ ] No hardcoded secrets (API keys, passwords, tokens)\n- [ ] All user inputs validated\n- [ ] SQL injection prevention (parameterized queries)\n- [ ] XSS prevention (sanitized HTML)\n- [ ] CSRF protection enabled\n- [ ] Authentication/authorization verified\n- [ ] Rate limiting on all endpoints\n- [ ] Error messages don't leak sensitive data\n\n## Secret Management\n\n- NEVER hardcode secrets in source code\n- ALWAYS use environment variables or a secret manager\n- Validate that required secrets are present at startup\n- Rotate any secrets that may have been exposed\n\n## Security Response Protocol\n\nIf security issue found:\n1. STOP immediately\n2. Use **security-reviewer** agent\n3. Fix CRITICAL issues before continuing\n4. Rotate any exposed secrets\n5. Review entire codebase for similar issues\n\n### common/testing\n\n# Testing Requirements\n\n## Minimum Test Coverage: 80%\n\nTest Types (ALL required):\n1. **Unit Tests** - Individual functions, utilities, components\n2. **Integration Tests** - API endpoints, database operations\n3. **E2E Tests** - Critical user flows (framework chosen per language)\n\n## Test-Driven Development\n\nMANDATORY workflow:\n1. Write test first (RED)\n2. Run test - it should FAIL\n3. Write minimal implementation (GREEN)\n4. Run test - it should PASS\n5. Refactor (IMPROVE)\n6. Verify coverage (80%+)\n\n## Troubleshooting Test Failures\n\n1. Use **tdd-guide** agent\n2. Check test isolation\n3. Verify mocks are correct\n4. Fix implementation, not tests (unless tests are wrong)\n\n## Agent Support\n\n- **tdd-guide** - Use PROACTIVELY for new features, enforces write-tests-first\n\n## Test Structure (AAA Pattern)\n\nPrefer Arrange-Act-Assert structure for tests:\n\n```typescript\ntest('calculates similarity correctly', () => {\n // Arrange\n const vector1 = [1, 0, 0]\n const vector2 = [0, 1, 0]\n\n // Act\n const similarity = calculateCosineSimilarity(vector1, vector2)\n\n // Assert\n expect(similarity).toBe(0)\n})\n```\n\n### Test Naming\n\nUse descriptive names that explain the behavior under test:\n\n```typescript\ntest('returns empty array when no markets match query', () => {})\ntest('throws error when API key is missing', () => {})\ntest('falls back to substring search when Redis is unavailable', () => {})\n```\n\n### cpp/coding-style\n\n# C++ Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with C++ specific content.\n\n## Modern C++ (C++17/20/23)\n\n- Prefer **modern C++ features** over C-style constructs\n- Use `auto` when the type is obvious from context\n- Use `constexpr` for compile-time constants\n- Use structured bindings: `auto [key, value] = map_entry;`\n\n## Resource Management\n\n- **RAII everywhere** — no manual `new`/`delete`\n- Use `std::unique_ptr` for exclusive ownership\n- Use `std::shared_ptr` only when shared ownership is truly needed\n- Use `std::make_unique` / `std::make_shared` over raw `new`\n\n## Naming Conventions\n\n- Types/Classes: `PascalCase`\n- Functions/Methods: `snake_case` or `camelCase` (follow project convention)\n- Constants: `kPascalCase` or `UPPER_SNAKE_CASE`\n- Namespaces: `lowercase`\n- Member variables: `snake_case_` (trailing underscore) or `m_` prefix\n\n## Formatting\n\n- Use **clang-format** — no style debates\n- Run `clang-format -i <file>` before committing\n\n## Reference\n\nSee skill: `cpp-coding-standards` for comprehensive C++ coding standards and guidelines.\n\n### cpp/hooks\n\n# C++ Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with C++ specific content.\n\n## Build Hooks\n\nRun these checks before committing C++ changes:\n\n```bash\n# Format check\nclang-format --dry-run --Werror src/*.cpp src/*.hpp\n\n# Static analysis\nclang-tidy src/*.cpp -- -std=c++17\n\n# Build\ncmake --build build\n\n# Tests\nctest --test-dir build --output-on-failure\n```\n\n## Recommended CI Pipeline\n\n1. **clang-format** — formatting check\n2. **clang-tidy** — static analysis\n3. **cppcheck** — additional analysis\n4. **cmake build** — compilation\n5. **ctest** — test execution with sanitizers\n\n### cpp/patterns\n\n# C++ Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with C++ specific content.\n\n## RAII (Resource Acquisition Is Initialization)\n\nTie resource lifetime to object lifetime:\n\n```cpp\nclass FileHandle {\npublic:\n explicit FileHandle(const std::string& path) : file_(std::fopen(path.c_str(), \"r\")) {}\n ~FileHandle() { if (file_) std::fclose(file_); }\n FileHandle(const FileHandle&) = delete;\n FileHandle& operator=(const FileHandle&) = delete;\nprivate:\n std::FILE* file_;\n};\n```\n\n## Rule of Five/Zero\n\n- **Rule of Zero**: Prefer classes that need no custom destructor, copy/move constructors, or assignments\n- **Rule of Five**: If you define any of destructor/copy-ctor/copy-assign/move-ctor/move-assign, define all five\n\n## Value Semantics\n\n- Pass small/trivial types by value\n- Pass large types by `const&`\n- Return by value (rely on RVO/NRVO)\n- Use move semantics for sink parameters\n\n## Error Handling\n\n- Use exceptions for exceptional conditions\n- Use `std::optional` for values that may not exist\n- Use `std::expected` (C++23) or result types for expected failures\n\n## Reference\n\nSee skill: `cpp-coding-standards` for comprehensive C++ patterns and anti-patterns.\n\n### cpp/security\n\n# C++ Security\n\n> This file extends [common/security.md](../common/security.md) with C++ specific content.\n\n## Memory Safety\n\n- Never use raw `new`/`delete` — use smart pointers\n- Never use C-style arrays — use `std::array` or `std::vector`\n- Never use `malloc`/`free` — use C++ allocation\n- Avoid `reinterpret_cast` unless absolutely necessary\n\n## Buffer Overflows\n\n- Use `std::string` over `char*`\n- Use `.at()` for bounds-checked access when safety matters\n- Never use `strcpy`, `strcat`, `sprintf` — use `std::string` or `fmt::format`\n\n## Undefined Behavior\n\n- Always initialize variables\n- Avoid signed integer overflow\n- Never dereference null or dangling pointers\n- Use sanitizers in CI:\n ```bash\n cmake -DCMAKE_CXX_FLAGS=\"-fsanitize=address,undefined\" ..\n ```\n\n## Static Analysis\n\n- Use **clang-tidy** for automated checks:\n ```bash\n clang-tidy --checks='*' src/*.cpp\n ```\n- Use **cppcheck** for additional analysis:\n ```bash\n cppcheck --enable=all src/\n ```\n\n## Reference\n\nSee skill: `cpp-coding-standards` for detailed security guidelines.\n\n### cpp/testing\n\n# C++ Testing\n\n> This file extends [common/testing.md](../common/testing.md) with C++ specific content.\n\n## Framework\n\nUse **GoogleTest** (gtest/gmock) with **CMake/CTest**.\n\n## Running Tests\n\n```bash\ncmake --build build && ctest --test-dir build --output-on-failure\n```\n\n## Coverage\n\n```bash\ncmake -DCMAKE_CXX_FLAGS=\"--coverage\" -DCMAKE_EXE_LINKER_FLAGS=\"--coverage\" ..\ncmake --build .\nctest --output-on-failure\nlcov --capture --directory . --output-file coverage.info\n```\n\n## Sanitizers\n\nAlways run tests with sanitizers in CI:\n\n```bash\ncmake -DCMAKE_CXX_FLAGS=\"-fsanitize=address,undefined\" ..\n```\n\n## Reference\n\nSee skill: `cpp-testing` for detailed C++ testing patterns, TDD workflow, and GoogleTest/GMock usage.\n\n### csharp/coding-style\n\n# C# Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with C#-specific content.\n\n## Standards\n\n- Follow current .NET conventions and enable nullable reference types\n- Prefer explicit access modifiers on public and internal APIs\n- Keep files aligned with the primary type they define\n\n## Types and Models\n\n- Prefer `record` or `record struct` for immutable value-like models\n- Use `class` for entities or types with identity and lifecycle\n- Use `interface` for service boundaries and abstractions\n- Avoid `dynamic` in application code; prefer generics or explicit models\n\n```csharp\npublic sealed record UserDto(Guid Id, string Email);\n\npublic interface IUserRepository\n{\n Task<UserDto?> FindByIdAsync(Guid id, CancellationToken cancellationToken);\n}\n```\n\n## Immutability\n\n- Prefer `init` setters, constructor parameters, and immutable collections for shared state\n- Do not mutate input models in-place when producing updated state\n\n```csharp\npublic sealed record UserProfile(string Name, string Email);\n\npublic static UserProfile Rename(UserProfile profile, string name) =>\n profile with { Name = name };\n```\n\n## Async and Error Handling\n\n- Prefer `async`/`await` over blocking calls like `.Result` or `.Wait()`\n- Pass `CancellationToken` through public async APIs\n- Throw specific exceptions and log with structured properties\n\n```csharp\npublic async Task<Order> LoadOrderAsync(\n Guid orderId,\n CancellationToken cancellationToken)\n{\n try\n {\n return await repository.FindAsync(orderId, cancellationToken)\n ?? throw new InvalidOperationException($\"Order {orderId} was not found.\");\n }\n catch (Exception ex)\n {\n logger.LogError(ex, \"Failed to load order {OrderId}\", orderId);\n throw;\n }\n}\n```\n\n## Formatting\n\n- Use `dotnet format` for formatting and analyzer fixes\n- Keep `using` directives organized and remove unused imports\n- Prefer expression-bodied members only when they stay readable\n\n### csharp/hooks\n\n# C# Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with C#-specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **dotnet format**: Auto-format edited C# files and apply analyzer fixes\n- **dotnet build**: Verify the solution or project still compiles after edits\n- **dotnet test --no-build**: Re-run the nearest relevant test project after behavior changes\n\n## Stop Hooks\n\n- Run a final `dotnet build` before ending a session with broad C# changes\n- Warn on modified `appsettings*.json` files so secrets do not get committed\n\n### csharp/patterns\n\n# C# Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with C#-specific content.\n\n## API Response Pattern\n\n```csharp\npublic sealed record ApiResponse<T>(\n bool Success,\n T? Data = default,\n string? Error = null,\n object? Meta = null);\n```\n\n## Repository Pattern\n\n```csharp\npublic interface IRepository<T>\n{\n Task<IReadOnlyList<T>> FindAllAsync(CancellationToken cancellationToken);\n Task<T?> FindByIdAsync(Guid id, CancellationToken cancellationToken);\n Task<T> CreateAsync(T entity, CancellationToken cancellationToken);\n Task<T> UpdateAsync(T entity, CancellationToken cancellationToken);\n Task DeleteAsync(Guid id, CancellationToken cancellationToken);\n}\n```\n\n## Options Pattern\n\nUse strongly typed options for config instead of reading raw strings throughout the codebase.\n\n```csharp\npublic sealed class PaymentsOptions\n{\n public const string SectionName = \"Payments\";\n public required string BaseUrl { get; init; }\n public required string ApiKeySecretName { get; init; }\n}\n```\n\n## Dependency Injection\n\n- Depend on interfaces at service boundaries\n- Keep constructors focused; if a service needs too many dependencies, split responsibilities\n- Register lifetimes intentionally: singleton for stateless/shared services, scoped for request data, transient for lightweight pure workers\n\n### csharp/security\n\n# C# Security\n\n> This file extends [common/security.md](../common/security.md) with C#-specific content.\n\n## Secret Management\n\n- Never hardcode API keys, tokens, or connection strings in source code\n- Use environment variables, user secrets for local development, and a secret manager in production\n- Keep `appsettings.*.json` free of real credentials\n\n```csharp\n// BAD\nconst string ApiKey = \"sk-live-123\";\n\n// GOOD\nvar apiKey = builder.Configuration[\"OpenAI:ApiKey\"]\n ?? throw new InvalidOperationException(\"OpenAI:ApiKey is not configured.\");\n```\n\n## SQL Injection Prevention\n\n- Always use parameterized queries with ADO.NET, Dapper, or EF Core\n- Never concatenate user input into SQL strings\n- Validate sort fields and filter operators before using dynamic query composition\n\n```csharp\nconst string sql = \"SELECT * FROM Orders WHERE CustomerId = @customerId\";\nawait connection.QueryAsync<Order>(sql, new { customerId });\n```\n\n## Input Validation\n\n- Validate DTOs at the application boundary\n- Use data annotations, FluentValidation, or explicit guard clauses\n- Reject invalid model state before running business logic\n\n## Authentication and Authorization\n\n- Prefer framework auth handlers instead of custom token parsing\n- Enforce authorization policies at endpoint or handler boundaries\n- Never log raw tokens, passwords, or PII\n\n## Error Handling\n\n- Return safe client-facing messages\n- Log detailed exceptions with structured context server-side\n- Do not expose stack traces, SQL text, or filesystem paths in API responses\n\n## References\n\nSee skill: `security-review` for broader application security review checklists.\n\n### csharp/testing\n\n# C# Testing\n\n> This file extends [common/testing.md](../common/testing.md) with C#-specific content.\n\n## Test Framework\n\n- Prefer **xUnit** for unit and integration tests\n- Use **FluentAssertions** for readable assertions\n- Use **Moq** or **NSubstitute** for mocking dependencies\n- Use **Testcontainers** when integration tests need real infrastructure\n\n## Test Organization\n\n- Mirror `src/` structure under `tests/`\n- Separate unit, integration, and end-to-end coverage clearly\n- Name tests by behavior, not implementation details\n\n```csharp\npublic sealed class OrderServiceTests\n{\n [Fact]\n public async Task FindByIdAsync_ReturnsOrder_WhenOrderExists()\n {\n // Arrange\n // Act\n // Assert\n }\n}\n```\n\n## ASP.NET Core Integration Tests\n\n- Use `WebApplicationFactory<TEntryPoint>` for API integration coverage\n- Test auth, validation, and serialization through HTTP, not by bypassing middleware\n\n## Coverage\n\n- Target 80%+ line coverage\n- Focus coverage on domain logic, validation, auth, and failure paths\n- Run `dotnet test` in CI with coverage collection enabled where available\n\n### dart/coding-style\n\n# Dart/Flutter Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Dart and Flutter-specific content.\n\n## Formatting\n\n- **dart format** for all `.dart` files — enforced in CI (`dart format --set-exit-if-changed .`)\n- Line length: 80 characters (dart format default)\n- Trailing commas on multi-line argument/parameter lists to improve diffs and formatting\n\n## Immutability\n\n- Prefer `final` for local variables and `const` for compile-time constants\n- Use `const` constructors wherever all fields are `final`\n- Return unmodifiable collections from public APIs (`List.unmodifiable`, `Map.unmodifiable`)\n- Use `copyWith()` for state mutations in immutable state classes\n\n```dart\n// BAD\nvar count = 0;\nList<String> items = ['a', 'b'];\n\n// GOOD\nfinal count = 0;\nconst items = ['a', 'b'];\n```\n\n## Naming\n\nFollow Dart conventions:\n- `camelCase` for variables, parameters, and named constructors\n- `PascalCase` for classes, enums, typedefs, and extensions\n- `snake_case` for file names and library names\n- `SCREAMING_SNAKE_CASE` for constants declared with `const` at top level\n- Prefix private members with `_`\n- Extension names describe the type they extend: `StringExtensions`, not `MyHelpers`\n\n## Null Safety\n\n- Avoid `!` (bang operator) — prefer `?.`, `??`, `if (x != null)`, or Dart 3 pattern matching; reserve `!` only where a null value is a programming error and crashing is the right behaviour\n- Avoid `late` unless initialization is guaranteed before first use (prefer nullable or constructor init)\n- Use `required` for constructor parameters that must always be provided\n\n```dart\n// BAD — crashes at runtime if user is null\nfinal name = user!.name;\n\n// GOOD — null-aware operators\nfinal name = user?.name ?? 'Unknown';\n\n// GOOD — Dart 3 pattern matching (exhaustive, compiler-checked)\nfinal name = switch (user) {\n User(:final name) => name,\n null => 'Unknown',\n};\n\n// GOOD — early-return null guard\nString getUserName(User? user) {\n if (user == null) return 'Unknown';\n return user.name; // promoted to non-null after the guard\n}\n```\n\n## Sealed Types and Pattern Matching (Dart 3+)\n\nUse sealed classes to model closed state hierarchies:\n\n```dart\nsealed class AsyncState<T> {\n const AsyncState();\n}\n\nfinal class Loading<T> extends AsyncState<T> {\n const Loading();\n}\n\nfinal class Success<T> extends AsyncState<T> {\n const Success(this.data);\n final T data;\n}\n\nfinal class Failure<T> extends AsyncState<T> {\n const Failure(this.error);\n final Object error;\n}\n```\n\nAlways use exhaustive `switch` with sealed types — no default/wildcard:\n\n```dart\n// BAD\nif (state is Loading) { ... }\n\n// GOOD\nreturn switch (state) {\n Loading() => const CircularProgressIndicator(),\n Success(:final data) => DataWidget(data),\n Failure(:final error) => ErrorWidget(error.toString()),\n};\n```\n\n## Error Handling\n\n- Specify exception types in `on` clauses — never use bare `catch (e)`\n- Never catch `Error` subtypes — they indicate programming bugs\n- Use `Result`-style types or sealed classes for recoverable errors\n- Avoid using exceptions for control flow\n\n```dart\n// BAD\ntry {\n await fetchUser();\n} catch (e) {\n log(e.toString());\n}\n\n// GOOD\ntry {\n await fetchUser();\n} on NetworkException catch (e) {\n log('Network error: ${e.message}');\n} on NotFoundException {\n handleNotFound();\n}\n```\n\n## Async / Futures\n\n- Always `await` Futures or explicitly call `unawaited()` to signal intentional fire-and-forget\n- Never mark a function `async` if it never `await`s anything\n- Use `Future.wait` / `Future.any` for concurrent operations\n- Check `context.mounted` before using `BuildContext` after any `await` (Flutter 3.7+)\n\n```dart\n// BAD — ignoring Future\nfetchData(); // fire-and-forget without marking intent\n\n// GOOD\nunawaited(fetchData()); // explicit fire-and-forget\nawait fetchData(); // or properly awaited\n```\n\n## Imports\n\n- Use `package:` imports throughout — never relative imports (`../`) for cross-feature or cross-layer code\n- Order: `dart:` → external `package:` → internal `package:` (same package)\n- No unused imports — `dart analyze` enforces this with `unused_import`\n\n## Code Generation\n\n- Generated files (`.g.dart`, `.freezed.dart`, `.gr.dart`) must be committed or gitignored consistently — pick one strategy per project\n- Never manually edit generated files\n- Keep generator annotations (`@JsonSerializable`, `@freezed`, `@riverpod`, etc.) on the canonical source file only\n\n### dart/hooks\n\n# Dart/Flutter Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Dart and Flutter-specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **dart format**: Auto-format `.dart` files after edit\n- **dart analyze**: Run static analysis after editing Dart files and surface warnings\n- **flutter test**: Optionally run affected tests after significant changes\n\n## Recommended Hook Configuration\n\n```json\n{\n \"hooks\": {\n \"PostToolUse\": [\n {\n \"matcher\": { \"tool_name\": \"Edit\", \"file_paths\": [\"**/*.dart\"] },\n \"hooks\": [\n { \"type\": \"command\", \"command\": \"dart format $CLAUDE_FILE_PATHS\" }\n ]\n }\n ]\n }\n}\n```\n\n## Pre-commit Checks\n\nRun before committing Dart/Flutter changes:\n\n```bash\ndart format --set-exit-if-changed .\ndart analyze --fatal-infos\nflutter test\n```\n\n## Useful One-liners\n\n```bash\n# Format all Dart files\ndart format .\n\n# Analyze and report issues\ndart analyze\n\n# Run all tests with coverage\nflutter test --coverage\n\n# Regenerate code-gen files\ndart run build_runner build --delete-conflicting-outputs\n\n# Check for outdated packages\nflutter pub outdated\n\n# Upgrade packages within constraints\nflutter pub upgrade\n```\n\n### dart/patterns\n\n# Dart/Flutter Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Dart, Flutter, and common ecosystem-specific content.\n\n## Repository Pattern\n\n```dart\nabstract interface class UserRepository {\n Future<User?> getById(String id);\n Future<List<User>> getAll();\n Stream<List<User>> watchAll();\n Future<void> save(User user);\n Future<void> delete(String id);\n}\n\nclass UserRepositoryImpl implements UserRepository {\n const UserRepositoryImpl(this._remote, this._local);\n\n final UserRemoteDataSource _remote;\n final UserLocalDataSource _local;\n\n @override\n Future<User?> getById(String id) async {\n final local = await _local.getById(id);\n if (local != null) return local;\n final remote = await _remote.getById(id);\n if (remote != null) await _local.save(remote);\n return remote;\n }\n\n @override\n Future<List<User>> getAll() async {\n final remote = await _remote.getAll();\n for (final user in remote) {\n await _local.save(user);\n }\n return remote;\n }\n\n @override\n Stream<List<User>> watchAll() => _local.watchAll();\n\n @override\n Future<void> save(User user) => _local.save(user);\n\n @override\n Future<void> delete(String id) async {\n await _remote.delete(id);\n await _local.delete(id);\n }\n}\n```\n\n## State Management: BLoC/Cubit\n\n```dart\n// Cubit — simple state transitions\nclass CounterCubit extends Cubit<int> {\n CounterCubit() : super(0);\n\n void increment() => emit(state + 1);\n void decrement() => emit(state - 1);\n}\n\n// BLoC — event-driven\n@immutable\nsealed class CartEvent {}\nclass CartItemAdded extends CartEvent { CartItemAdded(this.item); final Item item; }\nclass CartItemRemoved extends CartEvent { CartItemRemoved(this.id); final String id; }\nclass CartCleared extends CartEvent {}\n\n@immutable\nclass CartState {\n const CartState({this.items = const []});\n final List<Item> items;\n CartState copyWith({List<Item>? items}) => CartState(items: items ?? this.items);\n}\n\nclass CartBloc extends Bloc<CartEvent, CartState> {\n CartBloc() : super(const CartState()) {\n on<CartItemAdded>((event, emit) =>\n emit(state.copyWith(items: [...state.items, event.item])));\n on<CartItemRemoved>((event, emit) =>\n emit(state.copyWith(items: state.items.where((i) => i.id != event.id).toList())));\n on<CartCleared>((_, emit) => emit(const CartState()));\n }\n}\n```\n\n## State Management: Riverpod\n\n```dart\n// Simple provider\n@riverpod\nFuture<List<User>> users(Ref ref) async {\n final repo = ref.watch(userRepositoryProvider);\n return repo.getAll();\n}\n\n// Notifier for mutable state\n@riverpod\nclass CartNotifier extends _$CartNotifier {\n @override\n List<Item> build() => [];\n\n void add(Item item) => state = [...state, item];\n void remove(String id) => state = state.where((i) => i.id != id).toList();\n void clear() => state = [];\n}\n\n// ConsumerWidget\nclass CartPage extends ConsumerWidget {\n const CartPage({super.key});\n\n @override\n Widget build(BuildContext context, WidgetRef ref) {\n final items = ref.watch(cartNotifierProvider);\n return ListView(\n children: items.map((item) => CartItemTile(item: item)).toList(),\n );\n }\n}\n```\n\n## Dependency Injection\n\nConstructor injection is preferred. Use `get_it` or Riverpod providers at composition root:\n\n```dart\n// get_it registration (in a setup file)\nvoid setupDependencies() {\n final di = GetIt.instance;\n di.registerSingleton<ApiClient>(ApiClient(baseUrl: Env.apiUrl));\n di.registerSingleton<UserRepository>(\n UserRepositoryImpl(di<ApiClient>(), di<LocalDatabase>()),\n );\n di.registerFactory(() => UserListViewModel(di<UserRepository>()));\n}\n```\n\n## ViewModel Pattern (without BLoC/Riverpod)\n\n```dart\nclass UserListViewModel extends ChangeNotifier {\n UserListViewModel(this._repository);\n\n final UserRepository _repository;\n\n AsyncState<List<User>> _state = const Loading();\n AsyncState<List<User>> get state => _state;\n\n Future<void> load() async {\n _state = const Loading();\n notifyListeners();\n try {\n final users = await _repository.getAll();\n _state = Success(users);\n } on Exception catch (e) {\n _state = Failure(e);\n }\n notifyListeners();\n }\n}\n```\n\n## UseCase Pattern\n\n```dart\nclass GetUserUseCase {\n const GetUserUseCase(this._repository);\n final UserRepository _repository;\n\n Future<User?> call(String id) => _repository.getById(id);\n}\n\nclass CreateUserUseCase {\n const CreateUserUseCase(this._repository, this._idGenerator);\n final UserRepository _repository;\n final IdGenerator _idGenerator; // injected — domain layer must not depend on uuid package directly\n\n Future<void> call(CreateUserInput input) async {\n // Validate, apply business rules, then persist\n final user = User(id: _idGenerator.generate(), name: input.name, email: input.email);\n await _repository.save(user);\n }\n}\n```\n\n## Immutable State with freezed\n\n```dart\n@freezed\nclass UserState with _$UserState {\n const factory UserState({\n @Default([]) List<User> users,\n @Default(false) bool isLoading,\n String? errorMessage,\n }) = _UserState;\n}\n```\n\n## Clean Architecture Layer Boundaries\n\n```\nlib/\n├── domain/ # Pure Dart — no Flutter, no external packages\n│ ├── entities/\n│ ├── repositories/ # Abstract interfaces\n│ └── usecases/\n├── data/ # Implements domain interfaces\n│ ├── datasources/\n│ ├── models/ # DTOs with fromJson/toJson\n│ └── repositories/\n└── presentation/ # Flutter widgets + state management\n ├── pages/\n ├── widgets/\n └── providers/ (or blocs/ or viewmodels/)\n```\n\n- Domain must not import `package:flutter` or any data-layer package\n- Data layer maps DTOs to domain entities at repository boundaries\n- Presentation calls use cases, not repositories directly\n\n## Navigation (GoRouter)\n\n```dart\nfinal router = GoRouter(\n routes: [\n GoRoute(\n path: '/',\n builder: (context, state) => const HomePage(),\n ),\n GoRoute(\n path: '/users/:id',\n builder: (context, state) {\n final id = state.pathParameters['id']!;\n return UserDetailPage(userId: id);\n },\n ),\n ],\n // refreshListenable re-evaluates redirect whenever auth state changes\n refreshListenable: GoRouterRefreshStream(authCubit.stream),\n redirect: (context, state) {\n final isLoggedIn = context.read<AuthCubit>().state is AuthAuthenticated;\n if (!isLoggedIn && !state.matchedLocation.startsWith('/login')) {\n return '/login';\n }\n return null;\n },\n);\n```\n\n## References\n\nSee skill: `flutter-dart-code-review` for the comprehensive review checklist.\nSee skill: `compose-multiplatform-patterns` for Kotlin Multiplatform/Flutter interop patterns.\n\n### dart/security\n\n# Dart/Flutter Security\n\n> This file extends [common/security.md](../common/security.md) with Dart, Flutter, and mobile-specific content.\n\n## Secrets Management\n\n- Never hardcode API keys, tokens, or credentials in Dart source\n- Use `--dart-define` or `--dart-define-from-file` for compile-time config (values are not truly secret — use a backend proxy for server-side secrets)\n- Use `flutter_dotenv` or equivalent, with `.env` files listed in `.gitignore`\n- Store runtime secrets in platform-secure storage: `flutter_secure_storage` (Keychain on iOS, EncryptedSharedPreferences on Android)\n\n```dart\n// BAD\nconst apiKey = 'sk-abc123...';\n\n// GOOD — compile-time config (not secret, just configurable)\nconst apiKey = String.fromEnvironment('API_KEY');\n\n// GOOD — runtime secret from secure storage\nfinal token = await secureStorage.read(key: 'auth_token');\n```\n\n## Network Security\n\n- Enforce HTTPS — no `http://` calls in production\n- Configure Android `network_security_config.xml` to block cleartext traffic\n- Set `NSAppTransportSecurity` in `Info.plist` to disallow arbitrary loads\n- Set request timeouts on all HTTP clients — never leave defaults\n- Consider certificate pinning for high-security endpoints\n\n```dart\n// Dio with timeout and HTTPS enforcement\nfinal dio = Dio(BaseOptions(\n baseUrl: 'https://api.example.com',\n connectTimeout: const Duration(seconds: 10),\n receiveTimeout: const Duration(seconds: 30),\n));\n```\n\n## Input Validation\n\n- Validate and sanitize all user input before sending to API or storage\n- Never pass unsanitized input to SQL queries — use parameterized queries (sqflite, drift)\n- Sanitize deep link URLs before navigation — validate scheme, host, and path parameters\n- Use `Uri.tryParse` and validate before navigating\n\n```dart\n// BAD — SQL injection\nawait db.rawQuery(\"SELECT * FROM users WHERE email = '$userInput'\");\n\n// GOOD — parameterized\nawait db.query('users', where: 'email = ?', whereArgs: [userInput]);\n\n// BAD — unvalidated deep link\nfinal uri = Uri.parse(incomingLink);\ncontext.go(uri.path); // could navigate to any route\n\n// GOOD — validated deep link\nfinal uri = Uri.tryParse(incomingLink);\nif (uri != null && uri.host == 'myapp.com' && _allowedPaths.contains(uri.path)) {\n context.go(uri.path);\n}\n```\n\n## Data Protection\n\n- Store tokens, PII, and credentials only in `flutter_secure_storage`\n- Never write sensitive data to `SharedPreferences` or local files in plaintext\n- Clear auth state on logout: tokens, cached user data, cookies\n- Use biometric authentication (`local_auth`) for sensitive operations\n- Avoid logging sensitive data — no `print(token)` or `debugPrint(password)`\n\n## Android-Specific\n\n- Declare only required permissions in `AndroidManifest.xml`\n- Export Android components (`Activity`, `Service`, `BroadcastReceiver`) only when necessary; add `android:exported=\"false\"` where not needed\n- Review intent filters — exported components with implicit intent filters are accessible by any app\n- Use `FLAG_SECURE` for screens displaying sensitive data (prevents screenshots)\n\n```xml\n<!-- AndroidManifest.xml — restrict exported components -->\n<activity android:name=\".MainActivity\" android:exported=\"true\">\n <!-- Only the launcher activity needs exported=true -->\n</activity>\n<activity android:name=\".SensitiveActivity\" android:exported=\"false\" />\n```\n\n## iOS-Specific\n\n- Declare only required usage descriptions in `Info.plist` (`NSCameraUsageDescription`, etc.)\n- Store secrets in Keychain — `flutter_secure_storage` uses Keychain on iOS\n- Use App Transport Security (ATS) — disallow arbitrary loads\n- Enable data protection entitlement for sensitive files\n\n## WebView Security\n\n- Use `webview_flutter` v4+ (`WebViewController` / `WebViewWidget`) — the legacy `WebView` widget is removed\n- Disable JavaScript unless explicitly required (`JavaScriptMode.disabled`)\n- Validate URLs before loading — never load arbitrary URLs from deep links\n- Never expose Dart callbacks to JavaScript unless absolutely needed and carefully sandboxed\n- Use `NavigationDelegate.onNavigationRequest` to intercept and validate navigation requests\n\n```dart\n// webview_flutter v4+ API (WebViewController + WebViewWidget)\nfinal controller = WebViewController()\n ..setJavaScriptMode(JavaScriptMode.disabled) // disabled unless required\n ..setNavigationDelegate(\n NavigationDelegate(\n onNavigationRequest: (request) {\n final uri = Uri.tryParse(request.url);\n if (uri == null || uri.host != 'trusted.example.com') {\n return NavigationDecision.prevent;\n }\n return NavigationDecision.navigate;\n },\n ),\n );\n\n// In your widget tree:\nWebViewWidget(controller: controller)\n```\n\n## Obfuscation and Build Security\n\n- Enable obfuscation in release builds: `flutter build apk --obfuscate --split-debug-info=./debug-info/`\n- Keep `--split-debug-info` output out of version control (used for crash symbolication only)\n- Ensure ProGuard/R8 rules don't inadvertently expose serialized classes\n- Run `flutter analyze` and address all warnings before release\n\n### dart/testing\n\n# Dart/Flutter Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Dart and Flutter-specific content.\n\n## Test Framework\n\n- **flutter_test** / **dart:test** — built-in test runner\n- **mockito** (with `@GenerateMocks`) or **mocktail** (no codegen) for mocking\n- **bloc_test** for BLoC/Cubit unit tests\n- **fake_async** for controlling time in unit tests\n- **integration_test** for end-to-end device tests\n\n## Test Types\n\n| Type | Tool | Location | When to Write |\n|------|------|----------|---------------|\n| Unit | `dart:test` | `test/unit/` | All domain logic, state managers, repositories |\n| Widget | `flutter_test` | `test/widget/` | All widgets with meaningful behavior |\n| Golden | `flutter_test` | `test/golden/` | Design-critical UI components |\n| Integration | `integration_test` | `integration_test/` | Critical user flows on real device/emulator |\n\n## Unit Tests: State Managers\n\n### BLoC with `bloc_test`\n\n```dart\ngroup('CartBloc', () {\n late CartBloc bloc;\n late MockCartRepository repository;\n\n setUp(() {\n repository = MockCartRepository();\n bloc = CartBloc(repository);\n });\n\n tearDown(() => bloc.close());\n\n blocTest<CartBloc, CartState>(\n 'emits updated items when CartItemAdded',\n build: () => bloc,\n act: (b) => b.add(CartItemAdded(testItem)),\n expect: () => [CartState(items: [testItem])],\n );\n\n blocTest<CartBloc, CartState>(\n 'emits empty cart when CartCleared',\n seed: () => CartState(items: [testItem]),\n build: () => bloc,\n act: (b) => b.add(CartCleared()),\n expect: () => [const CartState()],\n );\n});\n```\n\n### Riverpod with `ProviderContainer`\n\n```dart\ntest('usersProvider loads users from repository', () async {\n final container = ProviderContainer(\n overrides: [userRepositoryProvider.overrideWithValue(FakeUserRepository())],\n );\n addTearDown(container.dispose);\n\n final result = await container.read(usersProvider.future);\n expect(result, isNotEmpty);\n});\n```\n\n## Widget Tests\n\n```dart\ntestWidgets('CartPage shows item count badge', (tester) async {\n await tester.pumpWidget(\n ProviderScope(\n overrides: [\n cartNotifierProvider.overrideWith(() => FakeCartNotifier([testItem])),\n ],\n child: const MaterialApp(home: CartPage()),\n ),\n );\n\n await tester.pump();\n expect(find.text('1'), findsOneWidget);\n expect(find.byType(CartItemTile), findsOneWidget);\n});\n\ntestWidgets('shows empty state when cart is empty', (tester) async {\n await tester.pumpWidget(\n ProviderScope(\n overrides: [cartNotifierProvider.overrideWith(() => FakeCartNotifier([]))],\n child: const MaterialApp(home: CartPage()),\n ),\n );\n\n await tester.pump();\n expect(find.text('Your cart is empty'), findsOneWidget);\n});\n```\n\n## Fakes Over Mocks\n\nPrefer hand-written fakes for complex dependencies:\n\n```dart\nclass FakeUserRepository implements UserRepository {\n final _users = <String, User>{};\n Object? fetchError;\n\n @override\n Future<User?> getById(String id) async {\n if (fetchError != null) throw fetchError!;\n return _users[id];\n }\n\n @override\n Future<List<User>> getAll() async {\n if (fetchError != null) throw fetchError!;\n return _users.values.toList();\n }\n\n @override\n Stream<List<User>> watchAll() => Stream.value(_users.values.toList());\n\n @override\n Future<void> save(User user) async {\n _users[user.id] = user;\n }\n\n @override\n Future<void> delete(String id) async {\n _users.remove(id);\n }\n\n void addUser(User user) => _users[user.id] = user;\n}\n```\n\n## Async Testing\n\n```dart\n// Use fake_async for controlling timers and Futures\ntest('debounce triggers after 300ms', () {\n fakeAsync((async) {\n final debouncer = Debouncer(delay: const Duration(milliseconds: 300));\n var callCount = 0;\n debouncer.run(() => callCount++);\n expect(callCount, 0);\n async.elapse(const Duration(milliseconds: 200));\n expect(callCount, 0);\n async.elapse(const Duration(milliseconds: 200));\n expect(callCount, 1);\n });\n});\n```\n\n## Golden Tests\n\n```dart\ntestWidgets('UserCard golden test', (tester) async {\n await tester.pumpWidget(\n MaterialApp(home: UserCard(user: testUser)),\n );\n\n await expectLater(\n find.byType(UserCard),\n matchesGoldenFile('goldens/user_card.png'),\n );\n});\n```\n\nRun `flutter test --update-goldens` when intentional visual changes are made.\n\n## Test Naming\n\nUse descriptive, behavior-focused names:\n\n```dart\ntest('returns null when user does not exist', () { ... });\ntest('throws NotFoundException when id is empty string', () { ... });\ntestWidgets('disables submit button while form is invalid', (tester) async { ... });\n```\n\n## Test Organization\n\n```\ntest/\n├── unit/\n│ ├── domain/\n│ │ └── usecases/\n│ └── data/\n│ └── repositories/\n├── widget/\n│ └── presentation/\n│ └── pages/\n└── golden/\n └── widgets/\n\nintegration_test/\n└── flows/\n ├── login_flow_test.dart\n └── checkout_flow_test.dart\n```\n\n## Coverage\n\n- Target 80%+ line coverage for business logic (domain + state managers)\n- All state transitions must have tests: loading → success, loading → error, retry\n- Run `flutter test --coverage` and inspect `lcov.info` with a coverage reporter\n- Coverage failures should block CI when below threshold\n\n### fsharp/coding-style\n\n# F# Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with F#-specific content.\n\n## Standards\n\n- Follow standard F# conventions and leverage the type system for correctness\n- Prefer immutability by default; use `mutable` only when justified by performance\n- Keep modules focused and cohesive\n\n## Types and Models\n\n- Prefer discriminated unions for domain modeling over class hierarchies\n- Use records for data with named fields\n- Use single-case unions for type-safe wrappers around primitives\n- Avoid classes unless interop or mutable state requires them\n\n```fsharp\ntype EmailAddress = EmailAddress of string\n\ntype OrderStatus =\n | Pending\n | Confirmed of confirmedAt: DateTimeOffset\n | Shipped of trackingNumber: string\n | Cancelled of reason: string\n\ntype Order =\n { Id: Guid\n CustomerId: string\n Status: OrderStatus\n Items: OrderItem list }\n```\n\n## Immutability\n\n- Records are immutable by default; use `with` expressions for updates\n- Prefer `list`, `map`, `set` over mutable collections\n- Avoid `ref` cells and mutable fields in domain logic\n\n```fsharp\nlet rename (profile: UserProfile) newName =\n { profile with Name = newName }\n```\n\n## Function Style\n\n- Prefer small, composable functions over large methods\n- Use the pipe operator `|>` to build readable data pipelines\n- Prefer pattern matching over if/else chains\n- Use `Option` instead of null; use `Result` for operations that can fail\n\n```fsharp\nlet processOrder order =\n order\n |> validateItems\n |> Result.bind calculateTotal\n |> Result.map applyDiscount\n |> Result.mapError OrderError\n```\n\n## Async and Error Handling\n\n- Use `task { }` for interop with .NET async APIs\n- Use `async { }` for F#-native async workflows\n- Propagate `CancellationToken` through public async APIs\n- Prefer `Result` and railway-oriented programming over exceptions for expected failures\n\n```fsharp\nlet loadOrderAsync (orderId: Guid) (ct: CancellationToken) =\n task {\n let! order = repository.FindAsync(orderId, ct)\n return\n order\n |> Option.defaultWith (fun () ->\n failwith $\"Order {orderId} was not found.\")\n }\n```\n\n## Formatting\n\n- Use `fantomas` for automatic formatting\n- Prefer significant whitespace; avoid unnecessary parentheses\n- Remove unused `open` declarations\n\n### Open Declaration Order\n\nGroup `open` statements into four sections separated by a blank line, each section sorted lexically within itself:\n\n1. `System.*`\n2. `Microsoft.*`\n3. Third-party namespaces\n4. First-party / project namespaces\n\n```fsharp\nopen System\nopen System.Collections.Generic\nopen System.Threading.Tasks\n\nopen Microsoft.AspNetCore.Http\nopen Microsoft.Extensions.Logging\n\nopen FsCheck.Xunit\nopen Swensen.Unquote\n\nopen MyApp.Domain\nopen MyApp.Infrastructure\n```\n\n### fsharp/hooks\n\n# F# Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with F#-specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **fantomas**: Auto-format edited F# files\n- **dotnet build**: Verify the solution or project still compiles after edits\n- **dotnet test --no-build**: Re-run the nearest relevant test project after behavior changes\n\n## Stop Hooks\n\n- Run a final `dotnet build` before ending a session with broad F# changes\n- Warn on modified `appsettings*.json` files so secrets do not get committed\n\n### fsharp/patterns\n\n# F# Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with F#-specific content.\n\n## Result Type for Error Handling\n\nUse `Result<'T, 'TError>` with railway-oriented programming instead of exceptions for expected failures.\n\n```fsharp\ntype OrderError =\n | InvalidCustomer of string\n | EmptyItems\n | ItemOutOfStock of sku: string\n\nlet validateOrder (request: CreateOrderRequest) : Result<ValidatedOrder, OrderError> =\n if String.IsNullOrWhiteSpace request.CustomerId then\n Error(InvalidCustomer \"CustomerId is required\")\n elif request.Items |> List.isEmpty then\n Error EmptyItems\n else\n Ok { CustomerId = request.CustomerId; Items = request.Items }\n```\n\n## Option for Missing Values\n\nPrefer `Option<'T>` over null. Use `Option.map`, `Option.bind`, and `Option.defaultValue` to transform.\n\n```fsharp\nlet findUser (id: Guid) : User option =\n users |> Map.tryFind id\n\nlet getUserEmail userId =\n findUser userId\n |> Option.map (fun u -> u.Email)\n |> Option.defaultValue \"unknown@example.com\"\n```\n\n## Discriminated Unions for Domain Modeling\n\nModel business states explicitly. The compiler enforces exhaustive handling.\n\n```fsharp\ntype PaymentState =\n | AwaitingPayment of amount: decimal\n | Paid of paidAt: DateTimeOffset * transactionId: string\n | Refunded of refundedAt: DateTimeOffset * reason: string\n | Failed of error: string\n\nlet describePayment = function\n | AwaitingPayment amount -> $\"Awaiting payment of {amount:C}\"\n | Paid (at, txn) -> $\"Paid at {at} (txn: {txn})\"\n | Refunded (at, reason) -> $\"Refunded at {at}: {reason}\"\n | Failed error -> $\"Payment failed: {error}\"\n```\n\n## Computation Expressions\n\nUse computation expressions to simplify sequential operations that may fail.\n\n```fsharp\nlet placeOrder request =\n result {\n let! validated = validateOrder request\n let! inventory = checkInventory validated.Items\n let! order = createOrder validated inventory\n return order\n }\n```\n\n## Module Organization\n\n- Group related functions in modules rather than classes\n- Use `[<RequireQualifiedAccess>]` to prevent name collisions\n- Keep modules small and focused on a single responsibility\n\n```fsharp\n[<RequireQualifiedAccess>]\nmodule Order =\n let create customerId items = { Id = Guid.NewGuid(); CustomerId = customerId; Items = items; Status = Pending }\n let confirm order = { order with Status = Confirmed(DateTimeOffset.UtcNow) }\n let cancel reason order = { order with Status = Cancelled reason }\n```\n\n## Dependency Injection\n\n- Define dependencies as function parameters or record-of-functions\n- Use interfaces sparingly, primarily at the boundary with .NET libraries\n- Prefer partial application for injecting dependencies into pipelines\n\n```fsharp\ntype OrderDeps =\n { FindOrder: Guid -> Task<Order option>\n SaveOrder: Order -> Task<unit>\n SendNotification: Order -> Task<unit> }\n\nlet processOrder (deps: OrderDeps) orderId =\n task {\n match! deps.FindOrder orderId with\n | None -> return Error \"Order not found\"\n | Some order ->\n let confirmed = Order.confirm order\n do! deps.SaveOrder confirmed\n do! deps.SendNotification confirmed\n return Ok confirmed\n }\n```\n\n### fsharp/security\n\n# F# Security\n\n> This file extends [common/security.md](../common/security.md) with F#-specific content.\n\n## Secret Management\n\n- Never hardcode API keys, tokens, or connection strings in source code\n- Use environment variables, user secrets for local development, and a secret manager in production\n- Keep `appsettings.*.json` free of real credentials\n\n```fsharp\n// BAD\nlet apiKey = \"sk-live-123\"\n\n// GOOD\nlet apiKey =\n configuration[\"OpenAI:ApiKey\"]\n |> Option.ofObj\n |> Option.defaultWith (fun () -> failwith \"OpenAI:ApiKey is not configured.\")\n```\n\n## SQL Injection Prevention\n\n- Always use parameterized queries with ADO.NET, Dapper, or EF Core\n- Never concatenate user input into SQL strings\n- Validate sort fields and filter operators before using dynamic query composition\n\n```fsharp\nlet findByCustomer (connection: IDbConnection) customerId =\n task {\n let sql = \"SELECT * FROM Orders WHERE CustomerId = @customerId\"\n return! connection.QueryAsync<Order>(sql, {| customerId = customerId |})\n }\n```\n\n## Input Validation\n\n- Validate inputs at the application boundary using types\n- Use single-case discriminated unions for validated values\n- Reject invalid input before it enters domain logic\n\n```fsharp\ntype ValidatedEmail = private ValidatedEmail of string\n\nmodule ValidatedEmail =\n let create (input: string) =\n if System.Text.RegularExpressions.Regex.IsMatch(input, @\"^[^@]+@[^@]+\\.[^@]+$\") then\n Ok(ValidatedEmail input)\n else\n Error \"Invalid email address\"\n\n let value (ValidatedEmail v) = v\n```\n\n## Authentication and Authorization\n\n- Prefer framework auth handlers instead of custom token parsing\n- Enforce authorization policies at endpoint or handler boundaries\n- Never log raw tokens, passwords, or PII\n\n## Error Handling\n\n- Return safe client-facing messages\n- Log detailed exceptions with structured context server-side\n- Do not expose stack traces, SQL text, or filesystem paths in API responses\n\n## References\n\nSee skill: `security-review` for broader application security review checklists.\n\n### fsharp/testing\n\n# F# Testing\n\n> This file extends [common/testing.md](../common/testing.md) with F#-specific content.\n\n## Test Framework\n\n- Prefer **xUnit** with **FsUnit.xUnit** for F#-friendly assertions\n- Use **Unquote** for quotation-based assertions with clear failure messages\n- Use **FsCheck.xUnit** for property-based testing\n- Use **NSubstitute** or function stubs for mocking dependencies\n- Use **Testcontainers** when integration tests need real infrastructure\n\n## Test Organization\n\n- Mirror `src/` structure under `tests/`\n- Separate unit, integration, and end-to-end coverage clearly\n- Name tests by behavior, not implementation details\n\n```fsharp\nopen Xunit\nopen Swensen.Unquote\n\n[<Fact>]\nlet ``PlaceOrder returns success when request is valid`` () =\n let request = { CustomerId = \"cust-123\"; Items = [ validItem ] }\n let result = OrderService.placeOrder request\n test <@ Result.isOk result @>\n\n[<Fact>]\nlet ``PlaceOrder returns error when items are empty`` () =\n let request = { CustomerId = \"cust-123\"; Items = [] }\n let result = OrderService.placeOrder request\n test <@ Result.isError result @>\n```\n\n## Property-Based Testing with FsCheck\n\n```fsharp\nopen FsCheck.Xunit\n\n[<Property>]\nlet ``order total is never negative`` (items: OrderItem list) =\n let total = Order.calculateTotal items\n total >= 0m\n```\n\n## ASP.NET Core Integration Tests\n\n- Use `WebApplicationFactory<TEntryPoint>` for API integration coverage\n- Test auth, validation, and serialization through HTTP, not by bypassing middleware\n\n## Coverage\n\n- Target 80%+ line coverage\n- Focus coverage on domain logic, validation, auth, and failure paths\n- Run `dotnet test` in CI with coverage collection enabled where available\n\n### golang/coding-style\n\n# Go Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Go specific content.\n\n## Formatting\n\n- **gofmt** and **goimports** are mandatory — no style debates\n\n## Design Principles\n\n- Accept interfaces, return structs\n- Keep interfaces small (1-3 methods)\n\n## Error Handling\n\nAlways wrap errors with context:\n\n```go\nif err != nil {\n return fmt.Errorf(\"failed to create user: %w\", err)\n}\n```\n\n## Reference\n\nSee skill: `golang-patterns` for comprehensive Go idioms and patterns.\n\n### golang/hooks\n\n# Go Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Go specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **gofmt/goimports**: Auto-format `.go` files after edit\n- **go vet**: Run static analysis after editing `.go` files\n- **staticcheck**: Run extended static checks on modified packages\n\n### golang/patterns\n\n# Go Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Go specific content.\n\n## Functional Options\n\n```go\ntype Option func(*Server)\n\nfunc WithPort(port int) Option {\n return func(s *Server) { s.port = port }\n}\n\nfunc NewServer(opts ...Option) *Server {\n s := &Server{port: 8080}\n for _, opt := range opts {\n opt(s)\n }\n return s\n}\n```\n\n## Small Interfaces\n\nDefine interfaces where they are used, not where they are implemented.\n\n## Dependency Injection\n\nUse constructor functions to inject dependencies:\n\n```go\nfunc NewUserService(repo UserRepository, logger Logger) *UserService {\n return &UserService{repo: repo, logger: logger}\n}\n```\n\n## Reference\n\nSee skill: `golang-patterns` for comprehensive Go patterns including concurrency, error handling, and package organization.\n\n### golang/security\n\n# Go Security\n\n> This file extends [common/security.md](../common/security.md) with Go specific content.\n\n## Secret Management\n\n```go\napiKey := os.Getenv(\"OPENAI_API_KEY\")\nif apiKey == \"\" {\n log.Fatal(\"OPENAI_API_KEY not configured\")\n}\n```\n\n## Security Scanning\n\n- Use **gosec** for static security analysis:\n ```bash\n gosec ./...\n ```\n\n## Context & Timeouts\n\nAlways use `context.Context` for timeout control:\n\n```go\nctx, cancel := context.WithTimeout(ctx, 5*time.Second)\ndefer cancel()\n```\n\n### golang/testing\n\n# Go Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Go specific content.\n\n## Framework\n\nUse the standard `go test` with **table-driven tests**.\n\n## Race Detection\n\nAlways run with the `-race` flag:\n\n```bash\ngo test -race ./...\n```\n\n## Coverage\n\n```bash\ngo test -cover ./...\n```\n\n## Reference\n\nSee skill: `golang-testing` for detailed Go testing patterns and helpers.\n\n### java/coding-style\n\n# Java Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Java-specific content.\n\n## Formatting\n\n- **google-java-format** or **Checkstyle** (Google or Sun style) for enforcement\n- One public top-level type per file\n- Consistent indent: 2 or 4 spaces (match project standard)\n- Member order: constants, fields, constructors, public methods, protected, private\n\n## Immutability\n\n- Prefer `record` for value types (Java 16+)\n- Mark fields `final` by default — use mutable state only when required\n- Return defensive copies from public APIs: `List.copyOf()`, `Map.copyOf()`, `Set.copyOf()`\n- Copy-on-write: return new instances rather than mutating existing ones\n\n```java\n// GOOD — immutable value type\npublic record OrderSummary(Long id, String customerName, BigDecimal total) {}\n\n// GOOD — final fields, no setters\npublic class Order {\n private final Long id;\n private final List<LineItem> items;\n\n public List<LineItem> getItems() {\n return List.copyOf(items);\n }\n}\n```\n\n## Naming\n\nFollow standard Java conventions:\n- `PascalCase` for classes, interfaces, records, enums\n- `camelCase` for methods, fields, parameters, local variables\n- `SCREAMING_SNAKE_CASE` for `static final` constants\n- Packages: all lowercase, reverse domain (`com.example.app.service`)\n\n## Modern Java Features\n\nUse modern language features where they improve clarity:\n- **Records** for DTOs and value types (Java 16+)\n- **Sealed classes** for closed type hierarchies (Java 17+)\n- **Pattern matching** with `instanceof` — no explicit cast (Java 16+)\n- **Text blocks** for multi-line strings — SQL, JSON templates (Java 15+)\n- **Switch expressions** with arrow syntax (Java 14+)\n- **Pattern matching in switch** — exhaustive sealed type handling (Java 21+)\n\n```java\n// Pattern matching instanceof\nif (shape instanceof Circle c) {\n return Math.PI * c.radius() * c.radius();\n}\n\n// Sealed type hierarchy\npublic sealed interface PaymentMethod permits CreditCard, BankTransfer, Wallet {}\n\n// Switch expression\nString label = switch (status) {\n case ACTIVE -> \"Active\";\n case SUSPENDED -> \"Suspended\";\n case CLOSED -> \"Closed\";\n};\n```\n\n## Optional Usage\n\n- Return `Optional<T>` from finder methods that may have no result\n- Use `map()`, `flatMap()`, `orElseThrow()` — never call `get()` without `isPresent()`\n- Never use `Optional` as a field type or method parameter\n\n```java\n// GOOD\nreturn repository.findById(id)\n .map(ResponseDto::from)\n .orElseThrow(() -> new OrderNotFoundException(id));\n\n// BAD — Optional as parameter\npublic void process(Optional<String> name) {}\n```\n\n## Error Handling\n\n- Prefer unchecked exceptions for domain errors\n- Create domain-specific exceptions extending `RuntimeException`\n- Avoid broad `catch (Exception e)` unless at top-level handlers\n- Include context in exception messages\n\n```java\npublic class OrderNotFoundException extends RuntimeException {\n public OrderNotFoundException(Long id) {\n super(\"Order not found: id=\" + id);\n }\n}\n```\n\n## Streams\n\n- Use streams for transformations; keep pipelines short (3-4 operations max)\n- Prefer method references when readable: `.map(Order::getTotal)`\n- Avoid side effects in stream operations\n- For complex logic, prefer a loop over a convoluted stream pipeline\n\n## References\n\nSee skill: `java-coding-standards` for full coding standards with examples.\nSee skill: `jpa-patterns` for JPA/Hibernate entity design patterns.\n\n### java/hooks\n\n# Java Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Java-specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **google-java-format**: Auto-format `.java` files after edit\n- **checkstyle**: Run style checks after editing Java files\n- **./mvnw compile** or **./gradlew compileJava**: Verify compilation after changes\n\n### java/patterns\n\n# Java Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Java-specific content.\n\n## Repository Pattern\n\nEncapsulate data access behind an interface:\n\n```java\npublic interface OrderRepository {\n Optional<Order> findById(Long id);\n List<Order> findAll();\n Order save(Order order);\n void deleteById(Long id);\n}\n```\n\nConcrete implementations handle storage details (JPA, JDBC, in-memory for tests).\n\n## Service Layer\n\nBusiness logic in service classes; keep controllers and repositories thin:\n\n```java\npublic class OrderService {\n private final OrderRepository orderRepository;\n private final PaymentGateway paymentGateway;\n\n public OrderService(OrderRepository orderRepository, PaymentGateway paymentGateway) {\n this.orderRepository = orderRepository;\n this.paymentGateway = paymentGateway;\n }\n\n public OrderSummary placeOrder(CreateOrderRequest request) {\n var order = Order.from(request);\n paymentGateway.charge(order.total());\n var saved = orderRepository.save(order);\n return OrderSummary.from(saved);\n }\n}\n```\n\n## Constructor Injection\n\nAlways use constructor injection — never field injection:\n\n```java\n// GOOD — constructor injection (testable, immutable)\npublic class NotificationService {\n private final EmailSender emailSender;\n\n public NotificationService(EmailSender emailSender) {\n this.emailSender = emailSender;\n }\n}\n\n// BAD — field injection (untestable without reflection, requires framework magic)\npublic class NotificationService {\n @Inject // or @Autowired\n private EmailSender emailSender;\n}\n```\n\n## DTO Mapping\n\nUse records for DTOs. Map at service/controller boundaries:\n\n```java\npublic record OrderResponse(Long id, String customer, BigDecimal total) {\n public static OrderResponse from(Order order) {\n return new OrderResponse(order.getId(), order.getCustomerName(), order.getTotal());\n }\n}\n```\n\n## Builder Pattern\n\nUse for objects with many optional parameters:\n\n```java\npublic class SearchCriteria {\n private final String query;\n private final int page;\n private final int size;\n private final String sortBy;\n\n private SearchCriteria(Builder builder) {\n this.query = builder.query;\n this.page = builder.page;\n this.size = builder.size;\n this.sortBy = builder.sortBy;\n }\n\n public static class Builder {\n private String query = \"\";\n private int page = 0;\n private int size = 20;\n private String sortBy = \"id\";\n\n public Builder query(String query) { this.query = query; return this; }\n public Builder page(int page) { this.page = page; return this; }\n public Builder size(int size) { this.size = size; return this; }\n public Builder sortBy(String sortBy) { this.sortBy = sortBy; return this; }\n public SearchCriteria build() { return new SearchCriteria(this); }\n }\n}\n```\n\n## Sealed Types for Domain Models\n\n```java\npublic sealed interface PaymentResult permits PaymentSuccess, PaymentFailure {\n record PaymentSuccess(String transactionId, BigDecimal amount) implements PaymentResult {}\n record PaymentFailure(String errorCode, String message) implements PaymentResult {}\n}\n\n// Exhaustive handling (Java 21+)\nString message = switch (result) {\n case PaymentSuccess s -> \"Paid: \" + s.transactionId();\n case PaymentFailure f -> \"Failed: \" + f.errorCode();\n};\n```\n\n## API Response Envelope\n\nConsistent API responses:\n\n```java\npublic record ApiResponse<T>(boolean success, T data, String error) {\n public static <T> ApiResponse<T> ok(T data) {\n return new ApiResponse<>(true, data, null);\n }\n public static <T> ApiResponse<T> error(String message) {\n return new ApiResponse<>(false, null, message);\n }\n}\n```\n\n## References\n\nSee skill: `springboot-patterns` for Spring Boot architecture patterns.\nSee skill: `quarkus-patterns` for Quarkus architecture patterns with REST, Panache, and messaging.\nSee skill: `jpa-patterns` for entity design and query optimization.\n\n### java/security\n\n# Java Security\n\n> This file extends [common/security.md](../common/security.md) with Java-specific content.\n\n## Secrets Management\n\n- Never hardcode API keys, tokens, or credentials in source code\n- Use environment variables: `System.getenv(\"API_KEY\")`\n- Use a secret manager (Vault, AWS Secrets Manager) for production secrets\n- Keep local config files with secrets in `.gitignore`\n\n```java\n// BAD\nprivate static final String API_KEY = \"sk-abc123...\";\n\n// GOOD — environment variable\nString apiKey = System.getenv(\"PAYMENT_API_KEY\");\nObjects.requireNonNull(apiKey, \"PAYMENT_API_KEY must be set\");\n```\n\n## SQL Injection Prevention\n\n- Always use parameterized queries — never concatenate user input into SQL\n- Use `PreparedStatement` or your framework's parameterized query API\n- Validate and sanitize any input used in native queries\n\n```java\n// BAD — SQL injection via string concatenation\nStatement stmt = conn.createStatement();\nString sql = \"SELECT * FROM orders WHERE name = '\" + name + \"'\";\nstmt.executeQuery(sql);\n\n// GOOD — PreparedStatement with parameterized query\nPreparedStatement ps = conn.prepareStatement(\"SELECT * FROM orders WHERE name = ?\");\nps.setString(1, name);\n\n// GOOD — JDBC template\njdbcTemplate.query(\"SELECT * FROM orders WHERE name = ?\", mapper, name);\n```\n\n## Input Validation\n\n- Validate all user input at system boundaries before processing\n- Use Bean Validation (`@NotNull`, `@NotBlank`, `@Size`) on DTOs when using a validation framework\n- Sanitize file paths and user-provided strings before use\n- Reject input that fails validation with clear error messages\n\n```java\n// Validate manually in plain Java\npublic Order createOrder(String customerName, BigDecimal amount) {\n if (customerName == null || customerName.isBlank()) {\n throw new IllegalArgumentException(\"Customer name is required\");\n }\n if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {\n throw new IllegalArgumentException(\"Amount must be positive\");\n }\n return new Order(customerName, amount);\n}\n```\n\n## Authentication and Authorization\n\n- Never implement custom auth crypto — use established libraries\n- Store passwords with bcrypt or Argon2, never MD5/SHA1\n- Enforce authorization checks at service boundaries\n- Clear sensitive data from logs — never log passwords, tokens, or PII\n\n## Dependency Security\n\n- Run `mvn dependency:tree` or `./gradlew dependencies` to audit transitive dependencies\n- Use OWASP Dependency-Check or Snyk to scan for known CVEs\n- Keep dependencies updated — set up Dependabot or Renovate\n\n## Error Messages\n\n- Never expose stack traces, internal paths, or SQL errors in API responses\n- Map exceptions to safe, generic client messages at handler boundaries\n- Log detailed errors server-side; return generic messages to clients\n\n```java\n// Log the detail, return a generic message\ntry {\n return orderService.findById(id);\n} catch (OrderNotFoundException ex) {\n log.warn(\"Order not found: id={}\", id);\n return ApiResponse.error(\"Resource not found\"); // generic, no internals\n} catch (Exception ex) {\n log.error(\"Unexpected error processing order id={}\", id, ex);\n return ApiResponse.error(\"Internal server error\"); // never expose ex.getMessage()\n}\n```\n\n## References\n\nSee skill: `springboot-security` for Spring Security authentication and authorization patterns.\nSee skill: `quarkus-security` for Quarkus security with JWT/OIDC, RBAC, and CDI.\nSee skill: `security-review` for general security checklists.\n\n### java/testing\n\n# Java Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Java-specific content.\n\n## Test Framework\n\n- **JUnit 5** (`@Test`, `@ParameterizedTest`, `@Nested`, `@DisplayName`)\n- **AssertJ** for fluent assertions (`assertThat(result).isEqualTo(expected)`)\n- **Mockito** for mocking dependencies\n- **Testcontainers** for integration tests requiring databases or services\n\n## Test Organization\n\n```\nsrc/test/java/com/example/app/\n service/ # Unit tests for service layer\n controller/ # Web layer / API tests\n repository/ # Data access tests\n integration/ # Cross-layer integration tests\n```\n\nMirror the `src/main/java` package structure in `src/test/java`.\n\n## Unit Test Pattern\n\n```java\n@ExtendWith(MockitoExtension.class)\nclass OrderServiceTest {\n\n @Mock\n private OrderRepository orderRepository;\n\n private OrderService orderService;\n\n @BeforeEach\n void setUp() {\n orderService = new OrderService(orderRepository);\n }\n\n @Test\n @DisplayName(\"findById returns order when exists\")\n void findById_existingOrder_returnsOrder() {\n var order = new Order(1L, \"Alice\", BigDecimal.TEN);\n when(orderRepository.findById(1L)).thenReturn(Optional.of(order));\n\n var result = orderService.findById(1L);\n\n assertThat(result.customerName()).isEqualTo(\"Alice\");\n verify(orderRepository).findById(1L);\n }\n\n @Test\n @DisplayName(\"findById throws when order not found\")\n void findById_missingOrder_throws() {\n when(orderRepository.findById(99L)).thenReturn(Optional.empty());\n\n assertThatThrownBy(() -> orderService.findById(99L))\n .isInstanceOf(OrderNotFoundException.class)\n .hasMessageContaining(\"99\");\n }\n}\n```\n\n## Parameterized Tests\n\n```java\n@ParameterizedTest\n@CsvSource({\n \"100.00, 10, 90.00\",\n \"50.00, 0, 50.00\",\n \"200.00, 25, 150.00\"\n})\n@DisplayName(\"discount applied correctly\")\nvoid applyDiscount(BigDecimal price, int pct, BigDecimal expected) {\n assertThat(PricingUtils.discount(price, pct)).isEqualByComparingTo(expected);\n}\n```\n\n## Integration Tests\n\nUse Testcontainers for real database integration:\n\n```java\n@Testcontainers\nclass OrderRepositoryIT {\n\n @Container\n static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(\"postgres:16\");\n\n private OrderRepository repository;\n\n @BeforeEach\n void setUp() {\n var dataSource = new PGSimpleDataSource();\n dataSource.setUrl(postgres.getJdbcUrl());\n dataSource.setUser(postgres.getUsername());\n dataSource.setPassword(postgres.getPassword());\n repository = new JdbcOrderRepository(dataSource);\n }\n\n @Test\n void save_and_findById() {\n var saved = repository.save(new Order(null, \"Bob\", BigDecimal.ONE));\n var found = repository.findById(saved.getId());\n assertThat(found).isPresent();\n }\n}\n```\n\nFor Spring Boot integration tests, see skill: `springboot-tdd`.\nFor Quarkus integration tests, see skill: `quarkus-tdd`.\n\n## Test Naming\n\nUse descriptive names with `@DisplayName`:\n- `methodName_scenario_expectedBehavior()` for method names\n- `@DisplayName(\"human-readable description\")` for reports\n\n## Coverage\n\n- Target 80%+ line coverage\n- Use JaCoCo for coverage reporting\n- Focus on service and domain logic — skip trivial getters/config classes\n\n## References\n\nSee skill: `springboot-tdd` for Spring Boot TDD patterns with MockMvc and Testcontainers.\nSee skill: `quarkus-tdd` for Quarkus TDD patterns with REST Assured and Dev Services.\nSee skill: `java-coding-standards` for testing expectations.\n\n### kotlin/coding-style\n\n# Kotlin Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Kotlin-specific content.\n\n## Formatting\n\n- **ktlint** or **Detekt** for style enforcement\n- Official Kotlin code style (`kotlin.code.style=official` in `gradle.properties`)\n\n## Immutability\n\n- Prefer `val` over `var` — default to `val` and only use `var` when mutation is required\n- Use `data class` for value types; use immutable collections (`List`, `Map`, `Set`) in public APIs\n- Copy-on-write for state updates: `state.copy(field = newValue)`\n\n## Naming\n\nFollow Kotlin conventions:\n- `camelCase` for functions and properties\n- `PascalCase` for classes, interfaces, objects, and type aliases\n- `SCREAMING_SNAKE_CASE` for constants (`const val` or `@JvmStatic`)\n- Prefix interfaces with behavior, not `I`: `Clickable` not `IClickable`\n\n## Null Safety\n\n- Never use `!!` — prefer `?.`, `?:`, `requireNotNull()`, or `checkNotNull()`\n- Use `?.let {}` for scoped null-safe operations\n- Return nullable types from functions that can legitimately have no result\n\n```kotlin\n// BAD\nval name = user!!.name\n\n// GOOD\nval name = user?.name ?: \"Unknown\"\nval name = requireNotNull(user) { \"User must be set before accessing name\" }.name\n```\n\n## Sealed Types\n\nUse sealed classes/interfaces to model closed state hierarchies:\n\n```kotlin\nsealed interface UiState<out T> {\n data object Loading : UiState<Nothing>\n data class Success<T>(val data: T) : UiState<T>\n data class Error(val message: String) : UiState<Nothing>\n}\n```\n\nAlways use exhaustive `when` with sealed types — no `else` branch.\n\n## Extension Functions\n\nUse extension functions for utility operations, but keep them discoverable:\n- Place in a file named after the receiver type (`StringExt.kt`, `FlowExt.kt`)\n- Keep scope limited — don't add extensions to `Any` or overly generic types\n\n## Scope Functions\n\nUse the right scope function:\n- `let` — null check + transform: `user?.let { greet(it) }`\n- `run` — compute a result using receiver: `service.run { fetch(config) }`\n- `apply` — configure an object: `builder.apply { timeout = 30 }`\n- `also` — side effects: `result.also { log(it) }`\n- Avoid deep nesting of scope functions (max 2 levels)\n\n## Error Handling\n\n- Use `Result<T>` or custom sealed types\n- Use `runCatching {}` for wrapping throwable code\n- Never catch `CancellationException` — always rethrow it\n- Avoid `try-catch` for control flow\n\n```kotlin\n// BAD — using exceptions for control flow\nval user = try { repository.getUser(id) } catch (e: NotFoundException) { null }\n\n// GOOD — nullable return\nval user: User? = repository.findUser(id)\n```\n\n### kotlin/hooks\n\n# Kotlin Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Kotlin-specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **ktfmt/ktlint**: Auto-format `.kt` and `.kts` files after edit\n- **detekt**: Run static analysis after editing Kotlin files\n- **./gradlew build**: Verify compilation after changes\n\n### kotlin/patterns\n\n# Kotlin Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Kotlin and Android/KMP-specific content.\n\n## Dependency Injection\n\nPrefer constructor injection. Use Koin (KMP) or Hilt (Android-only):\n\n```kotlin\n// Koin — declare modules\nval dataModule = module {\n single<ItemRepository> { ItemRepositoryImpl(get(), get()) }\n factory { GetItemsUseCase(get()) }\n viewModelOf(::ItemListViewModel)\n}\n\n// Hilt — annotations\n@HiltViewModel\nclass ItemListViewModel @Inject constructor(\n private val getItems: GetItemsUseCase\n) : ViewModel()\n```\n\n## ViewModel Pattern\n\nSingle state object, event sink, one-way data flow:\n\n```kotlin\ndata class ScreenState(\n val items: List<Item> = emptyList(),\n val isLoading: Boolean = false\n)\n\nclass ScreenViewModel(private val useCase: GetItemsUseCase) : ViewModel() {\n private val _state = MutableStateFlow(ScreenState())\n val state = _state.asStateFlow()\n\n fun onEvent(event: ScreenEvent) {\n when (event) {\n is ScreenEvent.Load -> load()\n is ScreenEvent.Delete -> delete(event.id)\n }\n }\n}\n```\n\n## Repository Pattern\n\n- `suspend` functions return `Result<T>` or custom error type\n- `Flow` for reactive streams\n- Coordinate local + remote data sources\n\n```kotlin\ninterface ItemRepository {\n suspend fun getById(id: String): Result<Item>\n suspend fun getAll(): Result<List<Item>>\n fun observeAll(): Flow<List<Item>>\n}\n```\n\n## UseCase Pattern\n\nSingle responsibility, `operator fun invoke`:\n\n```kotlin\nclass GetItemUseCase(private val repository: ItemRepository) {\n suspend operator fun invoke(id: String): Result<Item> {\n return repository.getById(id)\n }\n}\n\nclass GetItemsUseCase(private val repository: ItemRepository) {\n suspend operator fun invoke(): Result<List<Item>> {\n return repository.getAll()\n }\n}\n```\n\n## expect/actual (KMP)\n\nUse for platform-specific implementations:\n\n```kotlin\n// commonMain\nexpect fun platformName(): String\nexpect class SecureStorage {\n fun save(key: String, value: String)\n fun get(key: String): String?\n}\n\n// androidMain\nactual fun platformName(): String = \"Android\"\nactual class SecureStorage {\n actual fun save(key: String, value: String) { /* EncryptedSharedPreferences */ }\n actual fun get(key: String): String? = null /* ... */\n}\n\n// iosMain\nactual fun platformName(): String = \"iOS\"\nactual class SecureStorage {\n actual fun save(key: String, value: String) { /* Keychain */ }\n actual fun get(key: String): String? = null /* ... */\n}\n```\n\n## Coroutine Patterns\n\n- Use `viewModelScope` in ViewModels, `coroutineScope` for structured child work\n- Use `stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), initialValue)` for StateFlow from cold Flows\n- Use `supervisorScope` when child failures should be independent\n\n## Builder Pattern with DSL\n\n```kotlin\nclass HttpClientConfig {\n var baseUrl: String = \"\"\n var timeout: Long = 30_000\n private val interceptors = mutableListOf<Interceptor>()\n\n fun interceptor(block: () -> Interceptor) {\n interceptors.add(block())\n }\n}\n\nfun httpClient(block: HttpClientConfig.() -> Unit): HttpClient {\n val config = HttpClientConfig().apply(block)\n return HttpClient(config)\n}\n\n// Usage\nval client = httpClient {\n baseUrl = \"https://api.example.com\"\n timeout = 15_000\n interceptor { AuthInterceptor(tokenProvider) }\n}\n```\n\n## References\n\nSee skill: `kotlin-coroutines-flows` for detailed coroutine patterns.\nSee skill: `android-clean-architecture` for module and layer patterns.\n\n### kotlin/security\n\n# Kotlin Security\n\n> This file extends [common/security.md](../common/security.md) with Kotlin and Android/KMP-specific content.\n\n## Secrets Management\n\n- Never hardcode API keys, tokens, or credentials in source code\n- Use `local.properties` (git-ignored) for local development secrets\n- Use `BuildConfig` fields generated from CI secrets for release builds\n- Use `EncryptedSharedPreferences` (Android) or Keychain (iOS) for runtime secret storage\n\n```kotlin\n// BAD\nval apiKey = \"sk-abc123...\"\n\n// GOOD — from BuildConfig (generated at build time)\nval apiKey = BuildConfig.API_KEY\n\n// GOOD — from secure storage at runtime\nval token = secureStorage.get(\"auth_token\")\n```\n\n## Network Security\n\n- Use HTTPS exclusively — configure `network_security_config.xml` to block cleartext\n- Pin certificates for sensitive endpoints using OkHttp `CertificatePinner` or Ktor equivalent\n- Set timeouts on all HTTP clients — never leave defaults (which may be infinite)\n- Validate and sanitize all server responses before use\n\n```xml\n<!-- res/xml/network_security_config.xml -->\n<network-security-config>\n <base-config cleartextTrafficPermitted=\"false\" />\n</network-security-config>\n```\n\n## Input Validation\n\n- Validate all user input before processing or sending to API\n- Use parameterized queries for Room/SQLDelight — never concatenate user input into SQL\n- Sanitize file paths from user input to prevent path traversal\n\n```kotlin\n// BAD — SQL injection\n@Query(\"SELECT * FROM items WHERE name = '$input'\")\n\n// GOOD — parameterized\n@Query(\"SELECT * FROM items WHERE name = :input\")\nfun findByName(input: String): List<ItemEntity>\n```\n\n## Data Protection\n\n- Use `EncryptedSharedPreferences` for sensitive key-value data on Android\n- Use `@Serializable` with explicit field names — don't leak internal property names\n- Clear sensitive data from memory when no longer needed\n- Use `@Keep` or ProGuard rules for serialized classes to prevent name mangling\n\n## Authentication\n\n- Store tokens in secure storage, not in plain SharedPreferences\n- Implement token refresh with proper 401/403 handling\n- Clear all auth state on logout (tokens, cached user data, cookies)\n- Use biometric authentication (`BiometricPrompt`) for sensitive operations\n\n## ProGuard / R8\n\n- Keep rules for all serialized models (`@Serializable`, Gson, Moshi)\n- Keep rules for reflection-based libraries (Koin, Retrofit)\n- Test release builds — obfuscation can break serialization silently\n\n## WebView Security\n\n- Disable JavaScript unless explicitly needed: `settings.javaScriptEnabled = false`\n- Validate URLs before loading in WebView\n- Never expose `@JavascriptInterface` methods that access sensitive data\n- Use `WebViewClient.shouldOverrideUrlLoading()` to control navigation\n\n### kotlin/testing\n\n# Kotlin Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Kotlin and Android/KMP-specific content.\n\n## Test Framework\n\n- **kotlin.test** for multiplatform (KMP) — `@Test`, `assertEquals`, `assertTrue`\n- **JUnit 4/5** for Android-specific tests\n- **Turbine** for testing Flows and StateFlow\n- **kotlinx-coroutines-test** for coroutine testing (`runTest`, `TestDispatcher`)\n\n## ViewModel Testing with Turbine\n\n```kotlin\n@Test\nfun `loading state emitted then data`() = runTest {\n val repo = FakeItemRepository()\n repo.addItem(testItem)\n val viewModel = ItemListViewModel(GetItemsUseCase(repo))\n\n viewModel.state.test {\n assertEquals(ItemListState(), awaitItem()) // initial state\n viewModel.onEvent(ItemListEvent.Load)\n assertTrue(awaitItem().isLoading) // loading\n assertEquals(listOf(testItem), awaitItem().items) // loaded\n }\n}\n```\n\n## Fakes Over Mocks\n\nPrefer hand-written fakes over mocking frameworks:\n\n```kotlin\nclass FakeItemRepository : ItemRepository {\n private val items = mutableListOf<Item>()\n var fetchError: Throwable? = null\n\n override suspend fun getAll(): Result<List<Item>> {\n fetchError?.let { return Result.failure(it) }\n return Result.success(items.toList())\n }\n\n override fun observeAll(): Flow<List<Item>> = flowOf(items.toList())\n\n fun addItem(item: Item) { items.add(item) }\n}\n```\n\n## Coroutine Testing\n\n```kotlin\n@Test\nfun `parallel operations complete`() = runTest {\n val repo = FakeRepository()\n val result = loadDashboard(repo)\n advanceUntilIdle()\n assertNotNull(result.items)\n assertNotNull(result.stats)\n}\n```\n\nUse `runTest` — it auto-advances virtual time and provides `TestScope`.\n\n## Ktor MockEngine\n\n```kotlin\nval mockEngine = MockEngine { request ->\n when (request.url.encodedPath) {\n \"/api/items\" -> respond(\n content = Json.encodeToString(testItems),\n headers = headersOf(HttpHeaders.ContentType, ContentType.Application.Json.toString())\n )\n else -> respondError(HttpStatusCode.NotFound)\n }\n}\n\nval client = HttpClient(mockEngine) {\n install(ContentNegotiation) { json() }\n}\n```\n\n## Room/SQLDelight Testing\n\n- Room: Use `Room.inMemoryDatabaseBuilder()` for in-memory testing\n- SQLDelight: Use `JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)` for JVM tests\n\n```kotlin\n@Test\nfun `insert and query items`() = runTest {\n val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)\n Database.Schema.create(driver)\n val db = Database(driver)\n\n db.itemQueries.insert(\"1\", \"Sample Item\", \"description\")\n val items = db.itemQueries.getAll().executeAsList()\n assertEquals(1, items.size)\n}\n```\n\n## Test Naming\n\nUse backtick-quoted descriptive names:\n\n```kotlin\n@Test\nfun `search with empty query returns all items`() = runTest { }\n\n@Test\nfun `delete item emits updated list without deleted item`() = runTest { }\n```\n\n## Test Organization\n\n```\nsrc/\n├── commonTest/kotlin/ # Shared tests (ViewModel, UseCase, Repository)\n├── androidUnitTest/kotlin/ # Android unit tests (JUnit)\n├── androidInstrumentedTest/kotlin/ # Instrumented tests (Room, UI)\n└── iosTest/kotlin/ # iOS-specific tests\n```\n\nMinimum test coverage: ViewModel + UseCase for every feature.\n\n### nuxt/coding-style\n\n# Nuxt Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Nuxt specific content.\n\n## Directory layout\n\n- Default `srcDir` is `app/`. Framework files live at `app/pages/`, `app/layouts/`, `app/middleware/`, `app/plugins/`, `app/app.config.ts`. `nuxt.config.ts` and `server/` stay at project root.\n- Some projects override `srcDir` to `src/` for a Feature-Sliced Design layout, remapping `dir.pages` (for example to `src/app/routes`), `dir.layouts`, and the `@`/`~` aliases. Always check `nuxt.config.ts` before assuming a path.\n\n## Auto-imports discipline\n\n- Composables in `app/composables/` and `server/utils/` auto-import. Do NOT manually import Nuxt composables (`useFetch`, `useState`, `navigateTo`) or `defineStore` / `storeToRefs`.\n- Do NOT add a standalone `vue-router` dep (Nuxt bundles v5) or hand-mount `createApp` / `createPinia` / `createRouter`. The framework wires these.\n\n## Compiler macros\n\n- `definePageMeta` is a compile-time macro. Static values only, no reactive data and no side-effect calls inside it.\n- Augment typed `PageMeta` via `declare module '#app'` rather than casting.\n\n## Config file separation\n\nThree distinct files, do not conflate.\n\n- `nuxt.config.ts` = build-time only (`routeRules`, `modules`, `nitro`, `ssr` flag). Not reactive.\n- `runtimeConfig` (inside nuxt.config) = per-env runtime values, env-overridable via `NUXT_*`. Root keys are server-only, `public` keys are client-visible.\n- `app/app.config.ts` = public build-fixed reactive settings (theme tokens, feature flags). No env override. NEVER secrets.\n\n## Head and meta\n\n- `app.head` in `nuxt.config.ts` takes static values only.\n- Reactive meta goes through `useHead` / `useSeoMeta` in component setup, never via `app.head`.\n\n## Reference\n\n- ECC skills: `nuxt4-patterns`, `vite-patterns`, `frontend-patterns`.\n- [Nuxt directory structure](https://nuxt.com/docs/guide/directory-structure/app)\n- [Nuxt configuration](https://nuxt.com/docs/api/nuxt-config)\n\n### nuxt/hooks\n\n# Nuxt Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Nuxt specific content.\n\nThese are Claude Code harness hooks for Nuxt work. They run via the harness, not Claude.\n\n## Typecheck\n\n- `nuxi typecheck` wraps `vue-tsc`. Requires `vue-tsc` + `typescript` dev deps.\n- Run on `.vue` / `.ts` edit or pre-commit. Typecheck is project-wide, so debounce it and wrap it in a timeout (mirror `web/hooks.md`, for example `timeout 60 nuxi typecheck`) so a hung type-check is reaped instead of accumulating across fast edits.\n\n## Lint\n\n- Use the `@nuxt/eslint` module (flat-config, project-aware, generates `.nuxt/eslint.config.mjs`).\n- Run `eslint .` or `eslint --fix`. This is the Nuxt-official ESLint integration, prefer it over hand-rolled configs.\n\n## Format\n\n- `prettier --write`, or enable stylistic rules in `@nuxt/eslint` to avoid a Prettier/ESLint conflict.\n- Pick one formatting authority. Do not run both Prettier and ESLint stylistic at once.\n\n## Suggested PostToolUse chain\n\n- On Edit to `app/**` and `server/**`: run `eslint --fix` then `timeout 60 nuxi typecheck`.\n- Order matters: lint-fix first (mutates the file), the timed typecheck second (verifies the result). Debouncing still applies.\n\n## Reference\n\n- ECC skills: `nuxt4-patterns`, `vite-patterns`.\n- [@nuxt/eslint module](https://eslint.nuxt.com/)\n- [nuxi typecheck](https://nuxt.com/docs/api/commands/typecheck)\n\n### nuxt/patterns\n\n# Nuxt Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Nuxt specific content.\n\n## Data-fetch selection\n\nLoad-bearing. Pick by render timing, not habit.\n\n- `useFetch(url)` = SSR-safe, URL-first initial/first-paint data. The default. Forwards the server result through the payload so there is no hydration double-fetch.\n- `useAsyncData(key, fn)` = SSR-safe, custom async logic (SDK / GraphQL / combined calls). The explicit key shares the result across components.\n- `$fetch` = client interactions only (form submit, button click, POST/PUT/DELETE). NOT SSR-safe, double-fetches if used for first paint.\n- Rule: `useFetch` / `useAsyncData` for anything rendered on first paint, `$fetch` only for event-driven mutations.\n\n## Shared state\n\n- `useState('key', () => init)` for SSR-safe shared state. Values must be JSON-serializable.\n- NEVER `export const x = ref()` at module scope. One shared instance leaks across concurrent SSR requests and causes a memory leak.\n- With `@pinia/nuxt`: Pinia for domain state, `useState` for small cross-component primitives.\n- Async server-side init goes in `callOnce(async () => {...})`, not as a side effect inside `useAsyncData`.\n\n## Nitro server routes\n\n- `server/api/*.{get,post}.ts` auto-register by path + method. Handler is `defineEventHandler((event) => ...)`.\n- Errors via `throw createError({ status, statusText })`. Prefer the Web-API `status` / `statusText` over deprecated `statusCode` / `statusMessage`.\n- `server/middleware/` must NOT return a response. Only mutate `event.context` or set headers.\n\n## Route middleware\n\n- `app/middleware/*.ts` with `defineNuxtRouteMiddleware((to, from) => ...)`.\n- Use the `to` / `from` args. Do NOT call `useRoute()` inside middleware.\n- `.global` suffix runs on every route. Return `navigateTo()` to redirect, `abortNavigation()` to stop.\n\n## Hydration-safe rendering\n\n- Route off `status` (`idle | pending | success | error`) for lazy fetches.\n- `useAsyncData` payload uses `devalue` (Date/Map/Set/refs survive). A `server/api` response is `JSON.stringify`-only, so define `toJSON()` for non-JSON types.\n- Shrink payload with `pick` / `transform`. This reduces serialized size, it does not skip the fetch.\n\n## Reference\n\n- ECC skills: `nuxt4-patterns`, `vite-patterns`, `frontend-patterns`.\n- [Nuxt data fetching](https://nuxt.com/docs/getting-started/data-fetching)\n- [Nuxt state management](https://nuxt.com/docs/getting-started/state-management)\n- [Nuxt server engine (Nitro)](https://nuxt.com/docs/guide/directory-structure/server)\n\n### nuxt/security\n\n# Nuxt Security\n\n> This file extends [common/security.md](../common/security.md) with Nuxt specific content.\n\n## runtimeConfig public vs private\n\n- Root `runtimeConfig` keys are server-only. `runtimeConfig.public` serializes into EVERY page payload (client-visible).\n- Secrets go at root only. Never put secrets in `app.config.ts` or `runtimeConfig.public`, both ship to the client bundle.\n- Official warning: \"Be careful not to expose runtime config keys to the client-side by either rendering them or passing them to `useState`.\"\n\n## Server-route input validation\n\n- Use h3 validating readers. Do NOT trust raw `readBody` / `getQuery` / `getRouterParam`.\n - `readValidatedBody(event, schema)` validates the body.\n - `getValidatedQuery(event, schema)` validates the query.\n - `getValidatedRouterParams(event, schema)` validates route params.\n- All accept a validation function or a Zod schema and throw on failure.\n\n## SSR payload leakage\n\n- Anything in `useState`, `useFetch` / `useAsyncData` results, or `runtimeConfig.public` is serialized into the client payload. Never write a secret into those.\n- Use `useServerSeoMeta` for server-only meta with no client cost.\n\n## Cookie and auth passthrough on SSR\n\n- Nuxt does NOT auto-attach the incoming user's cookies to outbound server-side `$fetch`.\n- Forward explicitly with `useRequestFetch()` (cleanest, pre-bound to request headers) or `useRequestHeaders(['cookie'])`.\n- Relay a backend `Set-Cookie` to the browser via `$fetch.raw` + `appendResponseHeader(event, 'set-cookie', ...)`.\n- socket.io is client-only (`.client.ts` plugin), never SSR.\n\n## SSRF on server $fetch\n\n- Server routes run with full network egress. Never pass user-controlled input directly into a server-side `$fetch` URL or host.\n- Validate the param first (h3 utilities above), allowlist the target, pin to `runtimeConfig.public.apiBase`, reject user-supplied absolute URLs.\n- Auto-trigger `/security-review` only for routes that make external network requests (server `$fetch`), handle auth tokens or credentials, or perform sensitive mutations or authorization checks. Examples: SSRF-prone proxy endpoints, token exchange or password reset, admin actions. Skip benign read-only routes that only accept validated query params.\n\n## Reference\n\n- ECC skills: `security-review`, `nuxt4-patterns`.\n- [Nuxt runtime config](https://nuxt.com/docs/guide/going-further/runtime-config)\n- [h3 request utils](https://v1.h3.dev/utils/request)\n\n### nuxt/testing\n\n# Nuxt Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Nuxt specific content.\n\nPackage: `@nuxt/test-utils`. Vitest-first for unit and component tests, with built-in Playwright browser E2E support. nuxt-vitest and vitest-environment-nuxt are superseded and folded into it.\n\n## Setup\n\n- Install dev deps: `@nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core`.\n- Config: `defineVitestConfig({ test: { environment: 'nuxt' } })` from `@nuxt/test-utils/config`. Use `defineVitestProject` for multi-project (separate unit / nuxt / e2e environments).\n- Add `@nuxt/test-utils/module` to `nuxt.config`. Per-file opt-in via `// @vitest-environment nuxt`.\n\n## Runtime helpers\n\nImport from `@nuxt/test-utils/runtime`.\n\n- `mountSuspended(component, opts)` mounts in the Nuxt env with async setup + plugin injection (accepts `@vue/test-utils` mount options + `route`).\n- `renderSuspended(component, opts)` is the Testing Library variant (needs `@testing-library/vue`).\n- `mockNuxtImport(name, factory)` mocks auto-imports (e.g. `useState`). Once per import per file, use `vi.hoisted()`.\n- `mockComponent(name, factory)` mocks by PascalCase name or path.\n- `registerEndpoint(path, handler|opts)` mocks a Nitro endpoint to test server routes or stub the backend. Supports method + `once`.\n\n## E2E helpers\n\nImport from `@nuxt/test-utils/e2e`.\n\n- `await setup({ rootDir, server, browser, ... })` inside the describe block (manages beforeAll/afterAll).\n- Then `$fetch(url)` (rendered HTML), `fetch(url)` (response object), `url(path)` (full URL with port), `createPage(url)` (Playwright).\n- Playwright integration: import `expect` / `test` from `@nuxt/test-utils/playwright`.\n\n## What to test how\n\n- Composables: mock auto-imports with `mockNuxtImport`, mount a host component via `mountSuspended` to exercise `useState` / `useFetch` in the Nuxt runtime.\n- Server routes: `registerEndpoint` to stub, or e2e `$fetch` / `fetch` against the real Nitro server.\n\n## Reference\n\n- ECC skills: `nuxt4-patterns`, `e2e-testing`, `vite-patterns`.\n- [Nuxt testing docs](https://nuxt.com/docs/getting-started/testing)\n- [@nuxt/test-utils npm](https://www.npmjs.com/package/@nuxt/test-utils)\n\n### perl/coding-style\n\n# Perl Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Perl-specific content.\n\n## Standards\n\n- Always `use v5.36` (enables `strict`, `warnings`, `say`, subroutine signatures)\n- Use subroutine signatures — never unpack `@_` manually\n- Prefer `say` over `print` with explicit newlines\n\n## Immutability\n\n- Use **Moo** with `is => 'ro'` and `Types::Standard` for all attributes\n- Never use blessed hashrefs directly — always use Moo/Moose accessors\n- **OO override note**: Moo `has` attributes with `builder` or `default` are acceptable for computed read-only values\n\n## Formatting\n\nUse **perltidy** with these settings:\n\n```\n-i=4 # 4-space indent\n-l=100 # 100 char line length\n-ce # cuddled else\n-bar # opening brace always right\n```\n\n## Linting\n\nUse **perlcritic** at severity 3 with themes: `core`, `pbp`, `security`.\n\n```bash\nperlcritic --severity 3 --theme 'core || pbp || security' lib/\n```\n\n## Reference\n\nSee skill: `perl-patterns` for comprehensive modern Perl idioms and best practices.\n\n### perl/hooks\n\n# Perl Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Perl-specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **perltidy**: Auto-format `.pl` and `.pm` files after edit\n- **perlcritic**: Run lint check after editing `.pm` files\n\n## Warnings\n\n- Warn about `print` in non-script `.pm` files — use `say` or a logging module (e.g., `Log::Any`)\n\n### perl/patterns\n\n# Perl Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Perl-specific content.\n\n## Repository Pattern\n\nUse **DBI** or **DBIx::Class** behind an interface:\n\n```perl\npackage MyApp::Repo::User;\nuse Moo;\n\nhas dbh => (is => 'ro', required => 1);\n\nsub find_by_id ($self, $id) {\n my $sth = $self->dbh->prepare('SELECT * FROM users WHERE id = ?');\n $sth->execute($id);\n return $sth->fetchrow_hashref;\n}\n```\n\n## DTOs / Value Objects\n\nUse **Moo** classes with **Types::Standard** (equivalent to Python dataclasses):\n\n```perl\npackage MyApp::DTO::User;\nuse Moo;\nuse Types::Standard qw(Str Int);\n\nhas name => (is => 'ro', isa => Str, required => 1);\nhas email => (is => 'ro', isa => Str, required => 1);\nhas age => (is => 'ro', isa => Int);\n```\n\n## Resource Management\n\n- Always use **three-arg open** with `autodie`\n- Use **Path::Tiny** for file operations\n\n```perl\nuse autodie;\nuse Path::Tiny;\n\nmy $content = path('config.json')->slurp_utf8;\n```\n\n## Module Interface\n\nUse `Exporter 'import'` with `@EXPORT_OK` — never `@EXPORT`:\n\n```perl\nuse Exporter 'import';\nour @EXPORT_OK = qw(parse_config validate_input);\n```\n\n## Dependency Management\n\nUse **cpanfile** + **carton** for reproducible installs:\n\n```bash\ncarton install\ncarton exec prove -lr t/\n```\n\n## Reference\n\nSee skill: `perl-patterns` for comprehensive modern Perl patterns and idioms.\n\n### perl/security\n\n# Perl Security\n\n> This file extends [common/security.md](../common/security.md) with Perl-specific content.\n\n## Taint Mode\n\n- Use `-T` flag on all CGI/web-facing scripts\n- Sanitize `%ENV` (`$ENV{PATH}`, `$ENV{CDPATH}`, etc.) before any external command\n\n## Input Validation\n\n- Use allowlist regex for untainting — never `/(.*)/s`\n- Validate all user input with explicit patterns:\n\n```perl\nif ($input =~ /\\A([a-zA-Z0-9_-]+)\\z/) {\n my $clean = $1;\n}\n```\n\n## File I/O\n\n- **Three-arg open only** — never two-arg open\n- Prevent path traversal with `Cwd::realpath`:\n\n```perl\nuse Cwd 'realpath';\nmy $safe_path = realpath($user_path);\ndie \"Path traversal\" unless $safe_path =~ m{\\A/allowed/directory/};\n```\n\n## Process Execution\n\n- Use **list-form `system()`** — never single-string form\n- Use **IPC::Run3** for capturing output\n- Never use backticks with variable interpolation\n\n```perl\nsystem('grep', '-r', $pattern, $directory); # safe\n```\n\n## SQL Injection Prevention\n\nAlways use DBI placeholders — never interpolate into SQL:\n\n```perl\nmy $sth = $dbh->prepare('SELECT * FROM users WHERE email = ?');\n$sth->execute($email);\n```\n\n## Security Scanning\n\nRun **perlcritic** with the security theme at severity 4+:\n\n```bash\nperlcritic --severity 4 --theme security lib/\n```\n\n## Reference\n\nSee skill: `perl-security` for comprehensive Perl security patterns, taint mode, and safe I/O.\n\n### perl/testing\n\n# Perl Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Perl-specific content.\n\n## Framework\n\nUse **Test2::V0** for new projects (not Test::More):\n\n```perl\nuse Test2::V0;\n\nis($result, 42, 'answer is correct');\n\ndone_testing;\n```\n\n## Runner\n\n```bash\nprove -l t/ # adds lib/ to @INC\nprove -lr -j8 t/ # recursive, 8 parallel jobs\n```\n\nAlways use `-l` to ensure `lib/` is on `@INC`.\n\n## Coverage\n\nUse **Devel::Cover** — target 80%+:\n\n```bash\ncover -test\n```\n\n## Mocking\n\n- **Test::MockModule** — mock methods on existing modules\n- **Test::MockObject** — create test doubles from scratch\n\n## Pitfalls\n\n- Always end test files with `done_testing`\n- Never forget the `-l` flag with `prove`\n\n## Reference\n\nSee skill: `perl-testing` for detailed Perl TDD patterns with Test2::V0, prove, and Devel::Cover.\n\n### php/coding-style\n\n# PHP Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with PHP specific content.\n\n## Standards\n\n- Follow **PSR-12** formatting and naming conventions.\n- Prefer `declare(strict_types=1);` in application code.\n- Use scalar type hints, return types, and typed properties everywhere new code permits.\n\n## Immutability\n\n- Prefer immutable DTOs and value objects for data crossing service boundaries.\n- Use `readonly` properties or immutable constructors for request/response payloads where possible.\n- Keep arrays for simple maps; promote business-critical structures into explicit classes.\n\n## Formatting\n\n- Use **PHP-CS-Fixer** or **Laravel Pint** for formatting.\n- Use **PHPStan** or **Psalm** for static analysis.\n- Keep Composer scripts checked in so the same commands run locally and in CI.\n\n## Imports\n\n- Add `use` statements for all referenced classes, interfaces, and traits.\n- Avoid relying on the global namespace unless the project explicitly prefers fully qualified names.\n\n## Error Handling\n\n- Throw exceptions for exceptional states; avoid returning `false`/`null` as hidden error channels in new code.\n- Convert framework/request input into validated DTOs before it reaches domain logic.\n\n## Reference\n\nSee skill: `backend-patterns` for broader service/repository layering guidance.\n\n### php/hooks\n\n# PHP Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with PHP specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **Pint / PHP-CS-Fixer**: Auto-format edited `.php` files.\n- **PHPStan / Psalm**: Run static analysis after PHP edits in typed codebases.\n- **PHPUnit / Pest**: Run targeted tests for touched files or modules when edits affect behavior.\n\n## Warnings\n\n- Warn on `var_dump`, `dd`, `dump`, or `die()` left in edited files.\n- Warn when edited PHP files add raw SQL or disable CSRF/session protections.\n\n### php/patterns\n\n# PHP Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with PHP specific content.\n\n## Thin Controllers, Explicit Services\n\n- Keep controllers focused on transport: auth, validation, serialization, status codes.\n- Move business rules into application/domain services that are easy to test without HTTP bootstrapping.\n\n## DTOs and Value Objects\n\n- Replace shape-heavy associative arrays with DTOs for requests, commands, and external API payloads.\n- Use value objects for money, identifiers, date ranges, and other constrained concepts.\n\n## Dependency Injection\n\n- Depend on interfaces or narrow service contracts, not framework globals.\n- Pass collaborators through constructors so services are testable without service-locator lookups.\n\n## Boundaries\n\n- Isolate ORM models from domain decisions when the model layer is doing more than persistence.\n- Wrap third-party SDKs behind small adapters so the rest of the codebase depends on your contract, not theirs.\n\n## Reference\n\nSee skill: `api-design` for endpoint conventions and response-shape guidance.\nSee skill: `laravel-patterns` for Laravel-specific architecture guidance.\n\n### php/security\n\n# PHP Security\n\n> This file extends [common/security.md](../common/security.md) with PHP specific content.\n\n## Input and Output\n\n- Validate request input at the framework boundary (`FormRequest`, Symfony Validator, or explicit DTO validation).\n- Escape output in templates by default; treat raw HTML rendering as an exception that must be justified.\n- Never trust query params, cookies, headers, or uploaded file metadata without validation.\n\n## Database Safety\n\n- Use prepared statements (`PDO`, Doctrine, Eloquent query builder) for all dynamic queries.\n- Avoid string-building SQL in controllers/views.\n- Scope ORM mass-assignment carefully and whitelist writable fields.\n\n## Secrets and Dependencies\n\n- Load secrets from environment variables or a secret manager, never from committed config files.\n- Run `composer audit` in CI and review new package maintainer trust before adding dependencies.\n- Pin major versions deliberately and remove abandoned packages quickly.\n\n## Auth and Session Safety\n\n- Use `password_hash()` / `password_verify()` for password storage.\n- Regenerate session identifiers after authentication and privilege changes.\n- Enforce CSRF protection on state-changing web requests.\n\n## Reference\n\nSee skill: `laravel-security` for Laravel-specific security guidance.\n\n### php/testing\n\n# PHP Testing\n\n> This file extends [common/testing.md](../common/testing.md) with PHP specific content.\n\n## Framework\n\nUse **PHPUnit** as the default test framework. If **Pest** is configured in the project, prefer Pest for new tests and avoid mixing frameworks.\n\n## Coverage\n\n```bash\nvendor/bin/phpunit --coverage-text\n# or\nvendor/bin/pest --coverage\n```\n\nPrefer **pcov** or **Xdebug** in CI, and keep coverage thresholds in CI rather than as tribal knowledge.\n\n## Test Organization\n\n- Separate fast unit tests from framework/database integration tests.\n- Use factory/builders for fixtures instead of large hand-written arrays.\n- Keep HTTP/controller tests focused on transport and validation; move business rules into service-level tests.\n\n## Inertia\n\nIf the project uses Inertia.js, prefer `assertInertia` with `AssertableInertia` to verify component names and props instead of raw JSON assertions.\n\n## Reference\n\nSee skill: `tdd-workflow` for the repo-wide RED -> GREEN -> REFACTOR loop.\nSee skill: `laravel-tdd` for Laravel-specific testing patterns (PHPUnit and Pest).\n\n### python/coding-style\n\n# Python Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Python specific content.\n\n## Standards\n\n- Follow **PEP 8** conventions\n- Use **type annotations** on all function signatures\n\n## Immutability\n\nPrefer immutable data structures:\n\n```python\nfrom dataclasses import dataclass\n\n@dataclass(frozen=True)\nclass User:\n name: str\n email: str\n\nfrom typing import NamedTuple\n\nclass Point(NamedTuple):\n x: float\n y: float\n```\n\n## Formatting\n\n- **black** for code formatting\n- **isort** for import sorting\n- **ruff** for linting\n\n## Reference\n\nSee skill: `python-patterns` for comprehensive Python idioms and patterns.\n\n### python/fastapi\n\n# FastAPI Rules\n\nUse these rules for FastAPI projects alongside the general Python rules.\n\n## Structure\n\n- Put app construction in `create_app()`.\n- Keep routers thin; move persistence and business behavior into services or CRUD helpers.\n- Keep request schemas, update schemas, and response schemas separate.\n- Keep database sessions and auth in dependencies.\n\n## Async\n\n- Use `async def` for endpoints that perform I/O.\n- Use async database and HTTP clients from async endpoints.\n- Do not call `requests`, sync SQLAlchemy sessions, or blocking file/network operations from async routes.\n\n## Dependency Injection\n\n```python\n@router.get(\"/users/{user_id}\")\nasync def get_user(\n user_id: str,\n db: AsyncSession = Depends(get_db),\n current_user: User = Depends(get_current_user),\n):\n ...\n```\n\nDo not create `SessionLocal()` or long-lived clients inside route handlers.\n\n## Schemas\n\n- Never include passwords, password hashes, access tokens, refresh tokens, or internal auth state in response models.\n- Use `response_model` on endpoints that return application data.\n- Use field constraints instead of hand-written validation when Pydantic can express the rule.\n\n## Security\n\n- Keep CORS origins environment-specific.\n- Do not combine wildcard origins with credentialed CORS.\n- Validate JWT expiry, issuer, audience, and algorithm.\n- Rate-limit auth and write-heavy endpoints.\n- Redact credentials, cookies, authorization headers, and tokens from logs.\n\n## Testing\n\n- Override the exact dependency used by `Depends`.\n- Clear `app.dependency_overrides` after tests.\n- Prefer async test clients for async applications.\n\nSee skill: `fastapi-patterns`.\n\n### python/hooks\n\n# Python Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Python specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **black/ruff**: Auto-format `.py` files after edit\n- **mypy/pyright**: Run type checking after editing `.py` files\n\n## Warnings\n\n- Warn about `print()` statements in edited files (use `logging` module instead)\n\n### python/patterns\n\n# Python Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Python specific content.\n\n## Protocol (Duck Typing)\n\n```python\nfrom typing import Protocol\n\nclass Repository(Protocol):\n def find_by_id(self, id: str) -> dict | None: ...\n def save(self, entity: dict) -> dict: ...\n```\n\n## Dataclasses as DTOs\n\n```python\nfrom dataclasses import dataclass\n\n@dataclass\nclass CreateUserRequest:\n name: str\n email: str\n age: int | None = None\n```\n\n## Context Managers & Generators\n\n- Use context managers (`with` statement) for resource management\n- Use generators for lazy evaluation and memory-efficient iteration\n\n## Reference\n\nSee skill: `python-patterns` for comprehensive patterns including decorators, concurrency, and package organization.\n\n### python/security\n\n# Python Security\n\n> This file extends [common/security.md](../common/security.md) with Python specific content.\n\n## Secret Management\n\n```python\nimport os\nfrom dotenv import load_dotenv\n\nload_dotenv()\n\napi_key = os.environ[\"OPENAI_API_KEY\"] # Raises KeyError if missing\n```\n\n## Security Scanning\n\n- Use **bandit** for static security analysis:\n ```bash\n bandit -r src/\n ```\n\n## Reference\n\nSee skill: `django-security` for Django-specific security guidelines (if applicable).\n\n### python/testing\n\n# Python Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Python specific content.\n\n## Framework\n\nUse **pytest** as the testing framework.\n\n## Coverage\n\n```bash\npytest --cov=src --cov-report=term-missing\n```\n\n## Test Organization\n\nUse `pytest.mark` for test categorization:\n\n```python\nimport pytest\n\n@pytest.mark.unit\ndef test_calculate_total():\n ...\n\n@pytest.mark.integration\ndef test_database_connection():\n ...\n```\n\n## Reference\n\nSee skill: `python-testing` for detailed pytest patterns and fixtures.\n\n### react-native/accessibility\n\n# React Native / Expo Accessibility\n\n> Extends the ECC quality bar to accessibility (a11y). Treat a11y as a release requirement, not an afterthought.\n> Target: usable with screen readers (VoiceOver on iOS, TalkBack on Android) and at large font sizes.\n\n## Labeling\n\n- Every interactive element has an `accessibilityRole` and an `accessibilityLabel` (or readable child text).\n- Icon-only buttons MUST have an `accessibilityLabel` — there is no visible text for the reader to announce.\n- Use `accessibilityHint` only when the action is non-obvious; keep it short.\n- Group related elements with `accessible` on the container so they're announced as one unit when appropriate.\n\n```tsx\n<Pressable\n accessibilityRole=\"button\"\n accessibilityLabel=\"Delete item\"\n onPress={onDelete}\n>\n <TrashIcon />\n</Pressable>\n```\n\n## State & Live Regions\n\n- Communicate state with `accessibilityState` (e.g. `{ disabled, selected, checked, expanded }`).\n- Announce async/transient changes (toasts, validation errors) via `accessibilityLiveRegion` (Android) and `AccessibilityInfo.announceForAccessibility` where needed.\n- Reflect loading/error/empty states in text the reader can reach — not just spinners or color.\n\n## Touch Targets & Layout\n\n- Minimum touch target ~44x44pt (iOS) / 48x48dp (Android); use `hitSlop` to enlarge small controls.\n- Respect Dynamic Type / font scaling — avoid fixed heights that clip scaled text; test at the largest accessibility font size.\n- Honor `prefers-reduced-motion` (`AccessibilityInfo.isReduceMotionEnabled`) — gate non-essential animation.\n\n## Color & Contrast\n\n- Do not convey meaning by color alone; pair with text, icon, or shape.\n- Meet WCAG AA contrast: 4.5:1 for body text, 3:1 for large text and meaningful UI/graphical elements.\n- Verify both light and dark themes.\n\n## Focus & Navigation\n\n- Logical focus order; move focus to new content (modals, screens) on open and restore on close.\n- Ensure custom components are reachable and operable by the screen reader, not just by touch.\n\n## Testing\n\n- Manually test with VoiceOver and TalkBack on real devices — automated checks do not catch everything.\n- In component tests, query by role/label (see testing.md) so a11y and tests reinforce each other.\n- Add a11y to the pre-release gate: key flows pass a screen-reader walkthrough.\n\n### react-native/coding-style\n\n# React Native / Expo Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with React Native / Expo specific content.\n\n## Components\n\n- Define props with a named `interface` or `type`; do not use `React.FC`.\n- Keep screens thin: a screen composes hooks + presentational components, it does not hold heavy logic.\n- One component per file for anything reusable; co-locate small private subcomponents.\n- Prefer function components and hooks. No class components.\n\n```tsx\ninterface AvatarProps {\n uri: string\n size?: number\n onPress?: () => void\n}\n\nexport function Avatar({ uri, size = 40, onPress }: AvatarProps) {\n return (\n <Pressable onPress={onPress}>\n <Image source={{ uri }} style={{ width: size, height: size, borderRadius: size / 2 }} />\n </Pressable>\n )\n}\n```\n\n## Styling\n\nPick ONE styling system per project and stay consistent. `StyleSheet.create()` is the framework-native option; utility-class libraries (e.g. NativeWind) are a common alternative. This rule is library-agnostic — what matters is consistency and avoiding inline allocations.\n\n- StyleSheet: define styles with `StyleSheet.create()` at module scope — never build style objects inline inside `render`/JSX on hot paths (it allocates on every render).\n- Utility-class approach: extract repeated class strings into shared constants or a variant helper.\n- Never hardcode raw colors, spacing, or font sizes scattered across files. Centralize design tokens (theme file or config).\n\n```tsx\n// WRONG: inline style object recreated every render\n<View style={{ padding: 16, backgroundColor: '#fff' }} />\n\n// CORRECT (StyleSheet)\nconst styles = StyleSheet.create({ card: { padding: 16, backgroundColor: '#fff' } })\n<View style={styles.card} />\n\n// CORRECT (NativeWind)\n<View className=\"p-4 bg-white\" />\n```\n\n## Platform Differences\n\n- Use platform-specific files (`Component.ios.tsx`, `Component.android.tsx`) for substantial divergence.\n- Use `Platform.select()` / `Platform.OS` for small differences only.\n- Account for safe areas with `react-native-safe-area-context`; do not hardcode status bar / notch offsets.\n\n## Imports & Project Layout\n\n- Use the Expo/TS path alias (e.g. `@/components/...`) instead of long relative chains.\n- Organize by feature/domain, not by type. Keep files focused (200-400 lines typical, 800 max).\n\n## Logging\n\n- No `console.log` in shipped code. Use a logger and strip logs in production builds.\n- Surface user-facing errors through UI state, not console.\n\n## TypeScript\n\nAll TypeScript rules from `rules/typescript/` apply (explicit types on public APIs, avoid `any`, Zod for validation, immutable updates). This file only adds RN-specific guidance on top.\n\n### react-native/hooks\n\n# React Native / Expo Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with React Native / Expo-specific automation guidance.\n\nThese are recommended PostToolUse automations to keep RN/Expo code healthy. Wire them in your hook runtime (or run manually); adapt commands to your package manager.\n\n## Suggested PostToolUse checks (on edit of *.ts/*.tsx)\n\n- **Type check:** `tsc --noEmit` — catch type errors early.\n- **Lint:** `npx expo lint` (uses `eslint-config-expo`; flat config `eslint.config.js` is the default from SDK 53+).\n- **Format:** `prettier --write` on changed files.\n\n## Pre-release / periodic\n\n- `npx expo-doctor` — validates Expo/native dependency health and config.\n- `npx expo install --check` — keeps native deps aligned with the installed Expo SDK.\n- `npm audit` — dependency vulnerability scan.\n\n## Notes\n\n- Do not run heavy native builds inside fast edit hooks; keep edit-time hooks to typecheck/lint/format.\n- Reserve `eas build` / E2E for explicit commands or CI, not per-edit automation.\n- Keep these consistent with ECC hook runtime controls (`ECC_HOOK_PROFILE`, `ECC_DISABLED_HOOKS`).\n\n### react-native/patterns\n\n# React Native / Expo Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with React Native / Expo specific patterns.\n> Note: Do NOT install the `web/` ruleset in a React Native project — those patterns assume the DOM (e.g. URL-as-state) and do not apply here.\n\n## Navigation (Expo Router)\n\nExpo Router is Expo's built-in, file-based router (`app/` directory); React Navigation is the established alternative. The examples below use Expo Router; the principles apply either way.\n\n- Keep route files (`app/**`) thin — they wire params + hooks to a screen component that lives in `components/` or `features/`.\n- Type route params; validate untrusted params (e.g. from deep links) with Zod before use.\n- Use typed navigation helpers (`useLocalSearchParams`, `Link`, `router.push`).\n- Centralize linking config; never trust deep-link params without validation.\n\n```tsx\n// app/user/[id].tsx\nimport { useLocalSearchParams, router } from 'expo-router'\nimport { z } from 'zod'\n\nconst Params = z.object({ id: z.string().uuid() })\n\nexport default function UserScreen() {\n // Use safeParse, not parse: a malformed deep link would otherwise throw\n // during render and crash the screen. Redirect instead of throwing.\n const parsed = Params.safeParse(useLocalSearchParams())\n if (!parsed.success) {\n router.replace('/not-found')\n return null\n }\n return <UserProfile userId={parsed.data.id} />\n}\n```\n\n## State Management\n\nThe rule is to keep these concerns separate and not duplicate server data into client stores. The tools listed are common choices, not requirements — pick what fits your project.\n\n| Concern | Common choices |\n|---------|---------|\n| Server state | a server-cache library (TanStack Query, SWR) |\n| Client/UI state | a lightweight store (Zustand, Jotai) or Context |\n| Navigation/route state | Expo Router params (NOT a global store) |\n| Form state | a form library (e.g. React Hook Form) with schema validation |\n| Secure persistence | `expo-secure-store` |\n| Non-secure persistence | `AsyncStorage` / MMKV |\n\n- Derive values instead of storing redundant computed state.\n- Keep global client state minimal; prefer local `useState` until sharing is actually needed.\n\n## Data Fetching\n\nUse a server-cache library (TanStack Query, SWR) instead of ad-hoc fetch-in-`useEffect`. The examples use TanStack Query.\n\n- Route server reads through the cache (e.g. `useQuery`) and mutations through it (e.g. `useMutation`) with cache invalidation.\n- Validate API responses with Zod at the boundary; infer types from the schema. (Zod is already the validation default in ECC's `typescript/` rules.)\n- Handle the three states explicitly in UI: loading, error, empty.\n- Use optimistic updates for fast interactions: snapshot, apply, roll back on failure with visible feedback.\n- Fetch independent data in parallel; avoid request waterfalls between parent and child.\n\n```tsx\nfunction useUser(id: string) {\n return useQuery({\n queryKey: ['user', id],\n queryFn: async () => userSchema.parse(await api.getUser(id)),\n })\n}\n```\n\n## Lists\n\n- Use `FlatList`/`SectionList` (or `FlashList` for large/heavy lists) — never `.map()` a large array inside a `ScrollView`.\n- Provide a stable `keyExtractor`; memoize `renderItem`.\n- Paginate or virtualize long data sets.\n\n## Custom Hooks\n\n- Extract reusable logic (data, permissions, device APIs) into `use*` hooks.\n- Keep side effects (Expo SDK calls, subscriptions) inside hooks, not in JSX.\n\n## Async & Effects\n\n- Clean up subscriptions, timers, and listeners in the effect's return function.\n- Cancel or ignore stale async results on unmount to avoid setState-after-unmount.\n\n### react-native/performance\n\n# React Native / Expo Performance\n\n> This file extends [common/performance.md](../common/performance.md) with React Native / Expo specific content.\n\n## Rendering\n\n- Memoize expensive components with `React.memo`; memoize callbacks/values passed to children with `useCallback`/`useMemo` only where they prevent real re-renders.\n- Keep component state local and narrow — lifting state too high re-renders large subtrees.\n- Avoid creating new objects/arrays/functions inline in props on hot paths; they break memoization.\n- Split large screens so a state change re-renders the smallest possible subtree.\n\n## Lists\n\n- Use `FlatList`/`SectionList`, or `FlashList` (Shopify) for large or heterogeneous lists.\n- Provide `keyExtractor`, a memoized `renderItem`, and stable item heights when possible (`getItemLayout`).\n- Tune `initialNumToRender`, `windowSize`, `maxToRenderPerBatch` for heavy rows.\n- Never render large data sets with `.map()` inside a `ScrollView`.\n\n## Images & Assets\n\n- Use `expo-image` for caching, priority, and placeholders; serve appropriately sized images.\n- Avoid loading full-resolution images into small thumbnails.\n\n## Animations\n\n- Prefer `react-native-reanimated` (runs on the UI thread) over the JS-driven `Animated` API.\n- For legacy `Animated`, set `useNativeDriver: true` where supported.\n- Keep heavy computation off the JS thread; offload to Reanimated worklets or native modules.\n\n## Runtime & Build\n\n- Build on the **New Architecture** (Fabric + TurboModules). It is the default in recent Expo SDKs (opt-out still available on SDK 53–54) and is mandatory — cannot be disabled — from SDK 55+. Verify every native dependency is New-Arch compatible before shipping.\n- Ensure **Hermes** is enabled (default in modern Expo) for faster startup and lower memory.\n- Defer non-critical work after first paint; lazy-load heavy screens/modules.\n- Use `InteractionManager.runAfterInteractions` for work that can wait until animations finish.\n\n## Measuring\n\n- Profile with the React DevTools profiler, the Hermes sampling profiler, and the in-app performance monitor. (Avoid Flipper — it is deprecated and not supported on the New Architecture.)\n- Watch for: long lists without virtualization, oversized images, frequent full-tree re-renders, and synchronous work on the JS thread.\n\n### react-native/production-readiness\n\n# React Native / Expo Production Readiness\n\n> Extends the ECC philosophy to ship-grade concerns that style/pattern rules cannot encode by themselves.\n> A clean codebase is necessary but not sufficient for production — these items are mandatory before release.\n\n## Architecture\n\n- Ship on the **New Architecture** (Fabric + TurboModules). It is the default in recent Expo SDKs and is mandatory (cannot be disabled) from SDK 55+. Audit native deps for compatibility.\n- Pin the Expo SDK version; upgrade deliberately with `npx expo install --check` and test on both platforms.\n\n## Build & Release (EAS)\n\n- Use **EAS Build** for production binaries and **EAS Submit** for store delivery. Do not rely on local ad-hoc builds for release.\n- Keep separate build profiles (`development`, `preview`, `production`) in `eas.json`.\n- Manage signing credentials via EAS; never commit keystores or provisioning profiles.\n\n## Over-the-Air Updates\n\n- Use **EAS Update** (`expo-updates`) for JS-only fixes, with a defined runtime version policy.\n- Never push native changes via OTA — those require a new store build.\n- Roll out gradually and keep the ability to roll back.\n\n## Observability\n\n- Integrate crash + error reporting (e.g. **Sentry** via `@sentry/react-native`) in production builds.\n- Add structured logging and, where useful, analytics — but strip verbose logs from release.\n- Capture and surface failed network/mutation states; do not fail silently.\n\n## Configuration & Versioning\n\n- Bump `version` and `ios.buildNumber` / `android.versionCode` per release.\n- Public config via `EXPO_PUBLIC_*`; real secrets via EAS secrets only.\n- Validate required config at startup and fail fast with a clear message.\n\n## Pre-Release Gate\n\nBefore shipping, all must pass:\n\n- [ ] `tsc --noEmit` clean\n- [ ] `npx expo lint` clean\n- [ ] Tests green, coverage >= 80% (see testing.md)\n- [ ] `npx expo-doctor` healthy\n- [ ] Critical-flow E2E (Maestro/Detox) pass on a real build\n- [ ] No secrets in bundle (see security.md)\n- [ ] Crash reporting active and verified\n- [ ] Tested on physical iOS and Android devices, not just simulators\n\n### react-native/security\n\n# React Native / Expo Security\n\n> This file extends [common/security.md](../common/security.md) with React Native / Expo specific content.\n> The mandatory pre-commit checklist and Security Response Protocol from common/security.md still apply.\n\n## The Bundle Is Public\n\nTreat everything shipped in the app as readable by an attacker. A mobile binary can be unpacked.\n\n- NEVER ship real secrets (private API keys, service-role keys, signing secrets) in the JS bundle or `app.config`.\n- Public/anon keys (e.g. Supabase anon key, Firebase config) are acceptable ONLY when protected by server-side rules (RLS, security rules). Enforce authorization on the backend, never in the client.\n- Keep privileged operations behind your own server / edge functions.\n\n## Secret & Token Storage\n\n- Store auth tokens and sensitive values in `expo-secure-store` (Keychain / Keystore) — never in `AsyncStorage` or plain MMKV.\n- Do not persist secrets in Redux/Zustand state that may be serialized to disk.\n\n## Configuration\n\n- Read environment via `expo-constants` / `app.config.ts` `extra`, and `EXPO_PUBLIC_*` only for genuinely public values.\n- Keep build secrets in EAS secrets, not in the repo.\n\n## Network & Data\n\n- HTTPS only; reject cleartext. Consider certificate pinning for high-risk apps.\n- Validate ALL external data (API responses, deep-link params, push payloads) with Zod before use.\n- Validate and sanitize deep links and universal links — never route or grant access based on unvalidated params.\n\n## Permissions & Privacy\n\n- Request the minimum device permissions, at the moment they are needed, with clear rationale.\n- Declare data collection accurately for App Store / Play Store privacy disclosures.\n\n## Dependencies\n\n- Run `expo-doctor` and `npm audit` regularly; keep the Expo SDK and native deps current.\n- Use `/security-scan` (AgentShield) on the agent configuration itself.\n\n### react-native/testing\n\n# React Native / Expo Testing\n\n> This file extends [common/testing.md](../common/testing.md) with React Native / Expo specific content.\n> Coverage target and TDD workflow are inherited from common/testing.md (80% minimum, RED-GREEN-REFACTOR).\n\n## Tooling\n\n| Layer | Tool |\n|-------|------|\n| Unit / component | Jest + `@testing-library/react-native` (via `jest-expo` preset) |\n| Hooks | `@testing-library/react-native` `renderHook` |\n| E2E | Maestro (recommended, simple YAML flows) or Detox |\n| Type safety | `tsc --noEmit` in CI |\n\n## Component Tests\n\n- Query by accessible role/label/text, not by `testID` unless necessary — this also enforces accessibility.\n- Assert on user-visible behavior, not implementation details.\n- Follow Arrange-Act-Assert.\n\n```tsx\nimport { render, screen, fireEvent } from '@testing-library/react-native'\n\ntest('calls onSelect with the user id when pressed', () => {\n const onSelect = jest.fn()\n render(<UserCard user={{ id: '1', email: 'a@b.com' }} onSelect={onSelect} />)\n\n fireEvent.press(screen.getByText('a@b.com'))\n\n expect(onSelect).toHaveBeenCalledWith('1')\n})\n```\n\n## Mocking\n\n- Mock Expo SDK modules (camera, location, notifications, secure-store) at the test boundary.\n- Wrap components that use TanStack Query in a `QueryClientProvider` with a fresh client per test.\n- Mock navigation (`expo-router`) so screens render in isolation.\n\n## E2E\n\n- Cover critical flows only: auth, primary navigation, core transactions.\n- Run E2E on CI against a built app (EAS Build) before release.\n\n## What to test first\n\nUse the `tdd-guide` agent proactively for new features: write a failing test that captures the behavior, then implement.\n\n### react/coding-style\n\n# React Coding Style\n\n> This file extends [typescript/coding-style.md](../typescript/coding-style.md) and [common/coding-style.md](../common/coding-style.md) with React specific content.\n\n## File Extensions\n\n- `.tsx` for any file containing JSX, even one-liner snippets\n- `.ts` for pure logic, custom hooks without JSX, type definitions, utilities\n- `.test.tsx` / `.test.ts` mirroring the source file\n- Use `.jsx` only when the project intentionally avoids TypeScript — flag every new untyped React file in review\n\n## Naming\n\n- Components: `PascalCase` for both the symbol and the file (`UserCard.tsx`, default export `UserCard`)\n- Custom hooks: `useCamelCase` for the symbol, kebab-case for the file when the project convention is kebab-case (`use-debounce.ts` exports `useDebounce`)\n- Context: `<Domain>Context` symbol, `<Domain>Provider` provider component, `use<Domain>` consumer hook\n- Event handlers: `handleClick`, `handleSubmit` inside the component; the prop that receives it is `onClick`, `onSubmit`\n- Boolean props: `isLoading`, `hasError`, `canSubmit` — never `loading` or `error` alone for booleans\n\n## Component Shape\n\n```tsx\ntype Props = {\n user: User;\n onSelect: (id: string) => void;\n};\n\nexport function UserCard({ user, onSelect }: Props) {\n return (\n <button type=\"button\" onClick={() => onSelect(user.id)}>\n {user.name}\n </button>\n );\n}\n```\n\n- Prefer `type Props = {}` for closed component prop shapes\n- Use `interface` only when the prop type is extended via declaration merging or exported as a public API extension point\n- Always destructure props in the parameter list — no `props.user` access inside the body\n- Type the return implicitly through JSX (`function Foo(): JSX.Element` only when the function returns conditionally and the union confuses inference)\n\n## JSX\n\n- Self-close tags with no children: `<img />`, `<UserCard user={u} />`\n- Use fragments `<>...</>` over wrapper `<div>` when no DOM element is needed\n- Conditional rendering: `{condition && <Foo />}` for booleans, ternary for either/or, early return for guard clauses\n- Never put logic inline in JSX when it reads as multi-line — extract to a const above the return or a function\n\n```tsx\n// Prefer\nconst greeting = user.isAdmin ? \"Welcome, admin\" : `Hello ${user.name}`;\nreturn <h1>{greeting}</h1>;\n\n// Over\nreturn <h1>{user.isAdmin ? \"Welcome, admin\" : `Hello ${user.name}`}</h1>;\n```\n\n## Server / Client Boundary (Next.js App Router, RSC)\n\n- Default a new file to Server Component — only add `\"use client\"` when the file uses state, effects, refs, browser APIs, or event handlers\n- Place the `\"use client\"` directive on line 1, before any imports\n- Never import a Client Component file from inside a `\"use server\"` action file\n- Never re-export server-only code through a client module — the bundler will silently include it\n\n## Imports\n\n- React imports first: `import { useState } from \"react\"`\n- Then third-party libs, then absolute project imports, then relative\n- Type-only imports: `import type { ReactNode } from \"react\"` — never mix runtime and type imports in one statement when ESLint's `consistent-type-imports` is configured\n\n## Hooks Discipline\n\nSee [hooks.md](./hooks.md) for the full ruleset. Style highlights:\n\n- Custom hooks must start with `use` — enforced by `eslint-plugin-react-hooks`\n- Group all hook calls at the top of the component, before any conditional logic\n- Avoid creating ad-hoc hooks for one-line wrappers — inline the call instead\n\n## State\n\n- Local first (`useState`), lift only when shared\n- Context for cross-cutting state read by many components (theme, auth, i18n) — not for high-frequency updates\n- External store (Zustand, Jotai, Redux Toolkit) when state must persist across route changes, sync across tabs, or be debugged via devtools\n- Never duplicate state that can be derived — compute during render\n\n## Class Components\n\nForbidden in new code. Convert legacy class components to function components when touching them for non-trivial changes.\n\n## File Layout per Component\n\n```\ncomponents/UserCard/\n UserCard.tsx\n UserCard.module.css # or styled-components, or Tailwind classes inline\n UserCard.test.tsx\n index.ts # re-export only\n```\n\nInline single-file components are fine for trivial presentational pieces.\n\n### react/hooks\n\n# React Hooks\n\n> This file covers **React hooks** (`useState`, `useEffect`, `useMemo`, `useCallback`, custom hooks) — NOT the Claude Code `hooks/` runtime system. Naming matches the per-language convention `rules/<lang>/hooks.md` used across this repo.\n>\n> Extends [typescript/patterns.md](../typescript/patterns.md) and [common/patterns.md](../common/patterns.md).\n\n## Rules of Hooks\n\nEnforce `eslint-plugin-react-hooks` with `react-hooks/rules-of-hooks` set to error.\n\n1. Hooks only at the top level of a function component or another hook\n2. Never in loops, conditionals, nested functions, or after early returns\n3. Always called in the same order on every render\n4. Only inside React function components or custom hooks (functions starting with `use`)\n\n```tsx\n// WRONG: conditional hook\nfunction Foo({ enabled }: { enabled: boolean }) {\n if (enabled) {\n const [x, setX] = useState(0); // rule violation\n }\n}\n\n// CORRECT: hook unconditional, condition inside\nfunction Foo({ enabled }: { enabled: boolean }) {\n const [x, setX] = useState(0);\n if (!enabled) return null;\n return <span>{x}</span>;\n}\n```\n\n## `useEffect` — When NOT to Use\n\n`useEffect` is for synchronizing with external systems (subscriptions, browser APIs, third-party libraries). It is **not** the right tool for:\n\n- Derived state — compute it during render\n- Transforming data for rendering — compute it during render\n- Resetting state when a prop changes — use a `key` on the parent or derive from props\n- Notifying parents of state changes — call the callback in the event handler\n- Initializing app-level singletons — call the function module-side or in `main.tsx`\n\n```tsx\n// WRONG: effect for derived state\nconst [fullName, setFullName] = useState(\"\");\nuseEffect(() => {\n setFullName(`${first} ${last}`);\n}, [first, last]);\n\n// CORRECT: derive during render\nconst fullName = `${first} ${last}`;\n```\n\n## Dependency Arrays\n\n- Always include every reactive value referenced inside the effect/callback\n- Enable `react-hooks/exhaustive-deps` lint rule — never silence it without a comment explaining why\n- If the dep array grows unwieldy, the effect is doing too much — split it\n- Stable identity for functions passed in deps: wrap in `useCallback` only when the function is itself a dependency of another hook or passed to a memoized child\n\n## Cleanup\n\nEvery subscription, interval, listener, or in-flight request must clean up.\n\n```tsx\nuseEffect(() => {\n const controller = new AbortController();\n fetch(url, { signal: controller.signal }).then(handleResponse);\n return () => controller.abort();\n}, [url]);\n```\n\n```tsx\nuseEffect(() => {\n const id = setInterval(tick, 1000);\n return () => clearInterval(id);\n}, []);\n```\n\nMissing cleanup = race conditions when deps change, memory leaks on unmount.\n\n## `useMemo` and `useCallback` — When Worth It\n\nDefault position: **do not memoize**. Add `useMemo` / `useCallback` only when:\n\n1. The value is passed to a `React.memo`-wrapped child as a prop, and identity matters\n2. The value is a dependency of another `useEffect` / `useMemo` / `useCallback`\n3. The computation is measurably expensive (profile before assuming)\n\nPremature memoization adds noise, hides bugs, and can be slower than the recompute it replaces.\n\n## Custom Hooks\n\nExtract a custom hook when:\n\n- The same hook sequence (state + effect + computed) appears in 2+ components\n- The logic has a clear, nameable purpose (`useDebounce`, `useOnClickOutside`, `useLocalStorage`)\n- You want to test the logic independently of any component\n\nDo NOT extract when:\n\n- It would have a single caller — inline it\n- The \"hook\" is just `useState` with a different name — adds indirection, no value\n\n```tsx\nexport function useDebounce<T>(value: T, delay: number): T {\n const [debounced, setDebounced] = useState(value);\n useEffect(() => {\n const id = setTimeout(() => setDebounced(value), delay);\n return () => clearTimeout(id);\n }, [value, delay]);\n return debounced;\n}\n```\n\n## `useState` Patterns\n\n- Initial state from prop only at mount: pass a function `useState(() => computeInitial(prop))` when computation is expensive\n- Functional updater when the new state depends on the old: `setCount(c => c + 1)` — never `setCount(count + 1)` inside async or batched contexts\n- Group related state into one object only when they always change together; otherwise split into multiple `useState` calls\n- Use `useReducer` once state transitions are conditional on the previous state or there are 3+ related values\n\n## `useRef` Patterns\n\n- DOM refs for imperative APIs (focus, scroll, third-party libs)\n- Mutable container that does not trigger re-render (timer ids, previous values, \"is mounted\" flags)\n- Never read or write `ref.current` during render — only inside effects or event handlers\n- `useImperativeHandle` only when exposing a child API to a parent ref — last-resort escape hatch\n\n## `useSyncExternalStore`\n\nUse this hook to subscribe to any external store (browser API, third-party state lib, custom event emitter). It is the supported way to make external state safe with concurrent rendering.\n\n```tsx\nconst isOnline = useSyncExternalStore(\n (cb) => {\n window.addEventListener(\"online\", cb);\n window.addEventListener(\"offline\", cb);\n return () => {\n window.removeEventListener(\"online\", cb);\n window.removeEventListener(\"offline\", cb);\n };\n },\n () => navigator.onLine,\n () => true,\n);\n```\n\n## React 19 Additions\n\n- `use()` — unwrap promises and contexts inline; usable conditionally (only hook with that property)\n- `useFormStatus()` / `useFormState()` (or `useActionState`) — form submission state without prop drilling\n- `useOptimistic()` — optimistic UI updates while a server action is pending\n- `useTransition()` — mark non-urgent state updates so urgent ones stay responsive\n\nWhen the project targets React 19+, prefer these over hand-rolled equivalents.\n\n## Stale Closure Trap\n\nAsync handlers and intervals capture the values from the render where they were created. Fix by:\n\n1. Using the functional updater form of `setState`\n2. Putting the changing value in the dep array of `useEffect` and rebuilding the handler\n3. Reading from a ref that is kept in sync\n\n## Lint Configuration\n\nRequired rules:\n\n```json\n{\n \"rules\": {\n \"react-hooks/rules-of-hooks\": \"error\",\n \"react-hooks/exhaustive-deps\": \"warn\"\n }\n}\n```\n\nTreat `exhaustive-deps` warnings as errors in CI for new code.\n\n### react/patterns\n\n# React Patterns\n\n> This file extends [typescript/patterns.md](../typescript/patterns.md) and [common/patterns.md](../common/patterns.md) with React specific content. For hook-specific rules see [hooks.md](./hooks.md).\n\n## Container / Presentational Split\n\nContainer components own data fetching, state, and side effects. Presentational components receive props and render — no service calls, no hooks beyond local UI state.\n\n```tsx\n// Container — owns data\nexport function UserPage({ userId }: { userId: string }) {\n const { data: user, isLoading } = useUser(userId);\n if (isLoading) return <Spinner />;\n if (!user) return <NotFound />;\n return <UserCard user={user} onSelect={handleSelect} />;\n}\n\n// Presentational — pure\nexport function UserCard({ user, onSelect }: { user: User; onSelect: (id: string) => void }) {\n return <button onClick={() => onSelect(user.id)}>{user.name}</button>;\n}\n```\n\n## State Location Decision Tree\n\n1. Used by one component → `useState` inside it\n2. Used by parent + a few children → lift to nearest common ancestor, pass via props\n3. Used across distant branches → React Context **for low-frequency reads only** (theme, auth, locale)\n4. High-frequency updates shared across the tree → external store (Zustand, Jotai, Redux Toolkit)\n5. Server-derived data → server-state library (TanStack Query, SWR, RSC fetch) — not application state\n\nContext misused for frequently changing values causes every consumer to re-render on every update.\n\n## Server / Client Component Boundary (RSC, Next.js App Router)\n\n- Server Components are the default — they run on the server, do not ship to the client, and can `await` directly\n- Client Components opt in with `\"use client\"` at the top of the file\n- Data flows down: a Server Component can render a Client Component and pass serializable props\n- A Client Component cannot import a Server Component, but it can receive one via `children` or named slots\n\n```tsx\n// Server (default)\nexport default async function Page() {\n const user = await fetchUser();\n return <UserClient user={user} />;\n}\n\n// Client\n\"use client\";\nexport function UserClient({ user }: { user: User }) {\n const [tab, setTab] = useState(\"profile\");\n return <Tabs value={tab} onChange={setTab}>{user.name}</Tabs>;\n}\n```\n\n- Never import `\"server-only\"` packages (DB clients, secrets) from a Client Component file — wrap them in a Server Component or Server Action\n- Mark sensitive modules with `import \"server-only\"` so the bundler errors if a client file imports them\n\n## Suspense + Error Boundaries\n\nEvery Suspense boundary needs an Error Boundary above it. The pair handles both states.\n\n```tsx\n<ErrorBoundary fallback={<ErrorView />}>\n <Suspense fallback={<Skeleton />}>\n <UserDetails id={id} />\n </Suspense>\n</ErrorBoundary>\n```\n\n- Place Suspense boundaries close to where data is needed, not at the route root\n- Multiple narrower boundaries reveal loaded content progressively\n- Error Boundary must be a Class Component (React 19 has no functional equivalent yet) OR use a library wrapper such as `react-error-boundary`\n\n## Forms\n\n### Uncontrolled (React 19 + form actions)\n\nPrefer uncontrolled inputs with form actions when the form has a clear submit step. The browser owns the value; React reads it via `FormData` on submit.\n\n```tsx\nasync function action(formData: FormData) {\n \"use server\";\n await saveUser({ name: String(formData.get(\"name\")) });\n}\n\nexport function UserForm() {\n return (\n <form action={action}>\n <input name=\"name\" required />\n <button type=\"submit\">Save</button>\n </form>\n );\n}\n```\n\n### Controlled\n\nUse controlled inputs when the value drives other UI, requires real-time validation, or formatting.\n\n```tsx\nconst [email, setEmail] = useState(\"\");\nreturn <input value={email} onChange={(e) => setEmail(e.target.value)} />;\n```\n\n### Form Libraries\n\nFor complex forms (multi-step, dynamic field arrays, cross-field validation), use a library:\n\n- React Hook Form — minimal re-renders, uncontrolled-first\n- TanStack Form — typed, framework-agnostic\n- Final Form — when subscription-based re-renders matter\n\n## Data Fetching\n\n| Strategy | When |\n|---|---|\n| RSC fetch (`await` in Server Component) | Per-request data in Next.js App Router, no client-side cache needed |\n| TanStack Query | Client-side cache, mutations, optimistic updates, polling |\n| SWR | Lightweight cache + revalidation, simpler than TanStack Query |\n| `fetch` in `useEffect` | Avoid — race conditions, no cache, no retry. Only acceptable for one-off fire-and-forget |\n\nNever fetch in a `useEffect` when a real cache library is available — they handle deduping, cache invalidation, error retry, and Suspense integration.\n\n## Lists and Keys\n\n- `key` must be stable across renders — never `index` for any list that can reorder, insert, or delete\n- `key` must be unique among siblings, not globally\n- A reordered list with index keys causes state in child components to attach to the wrong row\n\n## Composition over Inheritance\n\n- Pass `children` for slot-style composition\n- Pass render-prop functions for parameterized rendering\n- Pass component types for plug-in points: `renderItem={UserRow}`\n- Never extend a component class to specialize behavior\n\n## Compound Components\n\nFor related controls (Tabs, Accordion, Menu), use compound components sharing state via Context:\n\n```tsx\n<Tabs defaultValue=\"profile\">\n <Tabs.List>\n <Tabs.Trigger value=\"profile\">Profile</Tabs.Trigger>\n <Tabs.Trigger value=\"settings\">Settings</Tabs.Trigger>\n </Tabs.List>\n <Tabs.Panel value=\"profile\"><ProfileForm /></Tabs.Panel>\n <Tabs.Panel value=\"settings\"><SettingsForm /></Tabs.Panel>\n</Tabs>\n```\n\n## Portals\n\nUse `createPortal` for modals, tooltips, toast containers — anything that must escape the parent's `overflow: hidden` or `z-index` stacking context. Render to a stable DOM node mounted in `index.html`.\n\n## Refs and Forwarding (React 19+)\n\nReact 19 lets function components accept `ref` as a regular prop — `forwardRef` is no longer required.\n\n```tsx\nexport function Input({ ref, ...rest }: { ref?: React.Ref<HTMLInputElement> } & InputProps) {\n return <input ref={ref} {...rest} />;\n}\n```\n\nOlder codebases on React 18 still need `forwardRef`.\n\n## Out of Scope (Pointer Sections)\n\n### Next.js (App Router)\n\n- Server Actions, Route Handlers, Middleware, Parallel/Intercepted Routes, streaming Metadata\n- Treated as a separate framework concern — when adding deep Next-specific patterns, propose a dedicated `rules/nextjs/` track\n- For now follow Next.js official docs for App Router specifics\n\n### React Native\n\n- Platform-specific imports (`Platform.OS`, `.ios.tsx` / `.android.tsx`), `StyleSheet`, navigation libraries (React Navigation, Expo Router)\n- Treated as a separate track — `rules/react-native/` is not yet present\n- React core hooks/patterns from this file still apply\n\n## Skill Reference\n\nFor React-specific deep dives see `skills/react-patterns/SKILL.md`. For cross-framework frontend concerns see `skills/frontend-patterns/SKILL.md`. For accessibility see `skills/accessibility/SKILL.md`.\n\n### react/security\n\n# React Security\n\n> This file extends [typescript/security.md](../typescript/security.md) and [common/security.md](../common/security.md) with React specific content.\n\n## XSS via `dangerouslySetInnerHTML`\n\nCRITICAL. The prop name is deliberately scary — treat every usage as a code review halt.\n\n```tsx\n// CRITICAL: unsanitized user input\n<div dangerouslySetInnerHTML={{ __html: userBio }} />\n\n// CORRECT options:\n// 1. Render as text\n<div>{userBio}</div>\n\n// 2. Render parsed markdown via a library that sanitizes\n<ReactMarkdown>{userBio}</ReactMarkdown>\n\n// 3. If raw HTML is required, sanitize first with DOMPurify\nimport DOMPurify from \"isomorphic-dompurify\";\n<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userBio) }} />\n```\n\nAudit checklist for every `dangerouslySetInnerHTML` call:\n\n- Is the input always under our control? Document the source.\n- If user-derived: is it sanitized at the **same call site**? (Sanitization at the API boundary is acceptable only if every consumer is verified.)\n- Is the sanitizer config allowlisting tags, not denylisting?\n\n## Unsafe URL Schemes\n\n`javascript:` and `data:` URLs in `href`, `src`, and `xlink:href` execute arbitrary code.\n\n```tsx\n// CRITICAL: javascript: URL injection\n<a href={user.website}>Visit</a> // if user.website = \"javascript:alert(1)\"\n\n// CORRECT: validate scheme\nfunction safeUrl(url: string): string | undefined {\n try {\n const parsed = new URL(url);\n if ([\"http:\", \"https:\", \"mailto:\"].includes(parsed.protocol)) return url;\n } catch {\n return undefined;\n }\n return undefined;\n}\n<a href={safeUrl(user.website)}>Visit</a>\n```\n\nReact warns about `javascript:` URLs in `href` in development mode, but does not block them at runtime. `data:` URLs and other schemes also slip through. Always validate.\n\n## `target=\"_blank\"` Without `rel`\n\n`<a target=\"_blank\">` without `rel=\"noopener noreferrer\"` lets the target page access `window.opener` and run navigation hijacks.\n\n```tsx\n// WRONG\n<a href={externalUrl} target=\"_blank\">External</a>\n\n// CORRECT\n<a href={externalUrl} target=\"_blank\" rel=\"noopener noreferrer\">External</a>\n```\n\nModern browsers default to `noopener` when `target=\"_blank\"`, but do not rely on browser defaults — be explicit.\n\n## Server Action Input Validation\n\nServer Actions (`\"use server\"`) run with the same trust level as a public API endpoint. Validate every input.\n\n```tsx\n\"use server\";\nimport { z } from \"zod\";\n\nconst Input = z.object({\n email: z.string().email(),\n age: z.number().int().min(0).max(120),\n});\n\nexport async function updateUser(_state: unknown, formData: FormData) {\n const parsed = Input.safeParse({\n email: formData.get(\"email\"),\n age: Number(formData.get(\"age\")),\n });\n if (!parsed.success) return { error: parsed.error.flatten() };\n // ...\n}\n```\n\n- Authenticate inside the action — do not trust the client-side route gate\n- Authorize: confirm the current user has permission for the specific record they are mutating\n- Rate limit sensitive actions\n\n## Secret Exposure via Env Vars\n\nPrefixed env vars are bundled into the client. Treat them as public.\n\n| Framework | Public prefix | Private |\n|---|---|---|\n| Next.js | `NEXT_PUBLIC_*` | All others |\n| Vite | `VITE_*` | `.env` server-side only |\n| Create React App | `REACT_APP_*`, plus `NODE_ENV` and `PUBLIC_URL` | All others (anything without the `REACT_APP_` prefix is server-side only) |\n| Remix | `process.env` access in `loader`/`action` only | Same |\n\n```ts\n// CRITICAL: secret leaked to client bundle\nconst apiKey = process.env.NEXT_PUBLIC_STRIPE_SECRET_KEY;\n```\n\nAudit on every PR that touches env vars: would this string in the public bundle be a problem?\n\n## Authentication / Authorization\n\n- Never store sessions in `localStorage` — accessible to any XSS. Use httpOnly secure cookies.\n- Never trust client-set state to gate sensitive UI. Render-gating in JSX prevents display, not access — the API must enforce.\n- CSRF: cookie-based auth requires CSRF tokens or `SameSite=Strict`/`Lax` cookies\n- Use double-submit cookies or origin verification for form actions when not using framework defaults\n\n## Content Security Policy (CSP)\n\nConfigure server-side. The minimum acceptable CSP for a React app:\n\n```\ndefault-src 'self';\nscript-src 'self' 'nonce-{REQUEST_NONCE}';\nstyle-src 'self' 'unsafe-inline';\nimg-src 'self' data: https:;\nconnect-src 'self' https://api.example.com;\nframe-ancestors 'none';\n```\n\n- Avoid `unsafe-inline` and `unsafe-eval` in `script-src`\n- For SSR with inline scripts (Next.js streaming, hydration data), use per-request nonces — both Next.js and Remix support nonce injection\n- `style-src 'unsafe-inline'` is often unavoidable for CSS-in-JS libraries — document the tradeoff\n\n## Prototype Pollution via Object Spread\n\n```tsx\n// WRONG: untrusted JSON spread directly into state\nconst update = await req.json();\nsetState({ ...state, ...update }); // attacker controls __proto__\n\n// CORRECT: parse with a schema, or guard keys\nconst Allowed = z.object({ name: z.string(), email: z.string().email() });\nconst parsed = Allowed.parse(await req.json());\nsetState({ ...state, ...parsed });\n```\n\n## SSR Template Injection\n\nWhen using `renderToString` or `renderToPipeableStream`:\n\n- All values rendered inside JSX are escaped by React — safe\n- Values passed to `dangerouslySetInnerHTML` are NOT escaped — same rules as client\n- Manually constructed HTML wrappers around the React output must be escaped or sanitized — never concatenate user input into the surrounding HTML template\n\n## Third-Party Components\n\n- Audit `npm audit` before adding any UI library\n- Check that the library does not internally use `dangerouslySetInnerHTML` on its input (e.g., rich text editors)\n- Pin versions, review changelogs before major upgrades\n- Be wary of components that accept HTML strings as props\n\n## Source Map Exposure in Production\n\nProduction builds should ship without source maps, or with sourcemaps uploaded to an error tracker (Sentry) and stripped from the public bundle. Public source maps leak internal logic and file structure.\n\n## Agent Support\n\n- Use `security-reviewer` agent for comprehensive security audits across the codebase\n- Use `react-reviewer` agent for React-specific patterns and the above rules in active code review\n\n### react/testing\n\n# React Testing\n\n> This file extends [typescript/testing.md](../typescript/testing.md) and [common/testing.md](../common/testing.md) with React specific content.\n\n## Library Choice\n\n- **React Testing Library (RTL)** — the standard for component testing. Tests behavior through the rendered DOM.\n- **Vitest** — preferred runner for new Vite-based projects. Faster than Jest, native ESM, same API.\n- **Jest** — still the default for Next.js / CRA projects. RTL works identically.\n- **Playwright Component Testing** — when component tests need a real browser engine (animation, layout, complex events)\n- **Cypress Component Testing** — alternative real-browser component runner\n\nPick one component test runner per project — do not mix RTL + Playwright CT in the same repo.\n\n## Core Principle\n\nTest what the user sees and does, not implementation details.\n\n- Query by accessible role first, then label, then text — fall back to `data-testid` only when nothing else fits\n- Never assert on internal state, props passed to children, or which hooks were called\n- Refactor without breaking tests = the test was testing behavior; that is the goal\n\n## Query Priority\n\nRTL exposes queries in three families. Use this priority order top-down:\n\n1. **Accessible to everyone**\n - `getByRole(role, { name })` — primary choice\n - `getByLabelText` — for form inputs\n - `getByPlaceholderText` — when no label is available (and add a label)\n - `getByText` — for non-interactive text\n - `getByDisplayValue` — for form fields with a current value\n\n2. **Semantic queries**\n - `getByAltText` — for images\n - `getByTitle` — last resort, low accessibility value\n\n3. **Test IDs**\n - `getByTestId(\"some-id\")` — escape hatch only, when none of the above work\n\n`getBy*` throws when no match. `queryBy*` returns null (use for asserting absence). `findBy*` returns a promise (use for async).\n\n## User Interaction\n\nPrefer `userEvent` over `fireEvent`. `userEvent` simulates real browser sequences (focus, keydown, beforeinput, input, keyup) — `fireEvent` dispatches a single synthetic event.\n\n```tsx\nimport userEvent from \"@testing-library/user-event\";\n\ntest(\"submits the form\", async () => {\n const user = userEvent.setup();\n render(<UserForm onSubmit={handleSubmit} />);\n\n await user.type(screen.getByLabelText(\"Email\"), \"user@example.com\");\n await user.click(screen.getByRole(\"button\", { name: /save/i }));\n\n expect(handleSubmit).toHaveBeenCalledWith({ email: \"user@example.com\" });\n});\n```\n\n- Always `await` `userEvent` calls — they are async\n- Call `userEvent.setup()` once at the top of each test, then reuse the returned `user`\n\n## Async Assertions\n\n```tsx\n// WRONG: synchronous query for async-rendered content\nexpect(screen.getByText(\"Loaded\")).toBeInTheDocument(); // throws — not in DOM yet\n\n// CORRECT: findBy* (returns a promise, retries)\nexpect(await screen.findByText(\"Loaded\")).toBeInTheDocument();\n\n// CORRECT: waitFor for non-element assertions\nawait waitFor(() => expect(saveSpy).toHaveBeenCalled());\n```\n\n- `findBy*` for async element appearance\n- `waitFor` for async expectations on side effects or other matchers\n- Never `setTimeout` + assertion — flaky\n\n## Network Mocking with MSW\n\nUse Mock Service Worker for any test that hits a network boundary. MSW runs at the network layer, so the component, hooks, and fetch library all behave as in production.\n\n```tsx\n// test setup\nimport { setupServer } from \"msw/node\";\nimport { http, HttpResponse } from \"msw\";\n\nconst server = setupServer(\n http.get(\"/api/users/:id\", ({ params }) =>\n HttpResponse.json({ id: params.id, name: \"Alice\" }),\n ),\n);\n\nbeforeAll(() => server.listen());\nafterEach(() => server.resetHandlers());\nafterAll(() => server.close());\n```\n\nPer-test override:\n\n```tsx\ntest(\"renders error on 500\", async () => {\n server.use(http.get(\"/api/users/:id\", () => new HttpResponse(null, { status: 500 })));\n render(<UserPage id=\"1\" />);\n expect(await screen.findByText(/something went wrong/i)).toBeInTheDocument();\n});\n```\n\n## Avoid Snapshot Tests for Components\n\nSnapshots of rendered output are brittle, hard to review, and rubber-stamped by reviewers. Use them only for:\n\n- Pure data serialization (e.g., a transformer that produces a stable string)\n- Catching unintended regressions in non-visual output\n\nFor component visual regression, use Playwright / Cypress / Percy screenshots — actual visual diffs, not DOM diffs.\n\n## Test Setup Helpers\n\nWrap providers once:\n\n```tsx\nfunction renderWithProviders(ui: React.ReactElement) {\n return render(\n <QueryClientProvider client={new QueryClient()}>\n <ThemeProvider theme={lightTheme}>\n <Router>{ui}</Router>\n </ThemeProvider>\n </QueryClientProvider>,\n );\n}\n```\n\nExport from `test-utils.tsx` and use everywhere.\n\n## Custom Hook Testing\n\nUse `renderHook` from RTL:\n\n```tsx\nimport { renderHook, act } from \"@testing-library/react\";\n\ntest(\"useCounter increments\", () => {\n const { result } = renderHook(() => useCounter());\n act(() => result.current.increment());\n expect(result.current.count).toBe(1);\n});\n```\n\n- Always wrap state-changing calls in `act`\n- Always test through the public hook API, not internal implementation\n\n## Accessibility Assertions\n\n```tsx\nimport { axe } from \"vitest-axe\"; // or jest-axe\n\ntest(\"UserCard has no a11y violations\", async () => {\n const { container } = render(<UserCard user={mockUser} />);\n expect(await axe(container)).toHaveNoViolations();\n});\n```\n\nRun axe assertions in component tests — catches missing labels, ARIA misuse, color contrast (limited).\n\n## When to Reach for Playwright / Cypress\n\nComponent test with RTL + JSDOM cannot:\n\n- Test real layout (flexbox, grid, viewport-dependent rendering)\n- Test scrolling, drag-and-drop, paste from clipboard\n- Test browser-native animation, CSS transitions\n- Test cross-frame interactions (iframes, popups)\n\nFor those, use Playwright Component Testing or end-to-end Playwright/Cypress runs. See [e2e-testing skill](../../skills/e2e-testing/SKILL.md).\n\n## Coverage Targets\n\n| Layer | Target |\n|---|---|\n| Pure utility functions | ≥90% |\n| Custom hooks | ≥85% |\n| Components (presentational) | ≥80% — behavior, not lines |\n| Container components | ≥70% — golden paths + error states |\n| Pages (E2E covered separately) | Smoke test per route minimum |\n\n## Anti-Patterns\n\n- Asserting on `container.querySelector` — bypasses accessibility queries\n- Asserting on number of renders — implementation detail\n- Mocking React hooks (`jest.mock(\"react\", ...)`) — refactor the component instead\n- Mocking child components by default — tests the integration, not the parent in isolation\n- Manual `act()` warnings ignored — they indicate real bugs\n\n## Skill Reference\n\nSee `skills/react-testing/SKILL.md` for end-to-end test examples, MSW patterns, and accessibility test scaffolding.\n\n### ruby/coding-style\n\n# Ruby Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Ruby and Rails specific content.\n\n## Standards\n\n- Target **Ruby 3.3+** for new Rails work unless the project already pins an older supported runtime.\n- Enable **YJIT** in production only after measuring boot time, memory, and request/job throughput.\n- Add `# frozen_string_literal: true` to new Ruby files when the project uses that convention.\n- Prefer clear Ruby over clever metaprogramming; isolate DSL-heavy code behind narrow, tested boundaries.\n\n## Formatting And Linting\n\n- Use the project's checked-in RuboCop config. For Rails 8+ apps, start from `rubocop-rails-omakase` and customize only where the codebase has a real convention.\n- Keep formatter/linter commands behind binstubs or scripts so CI and local runs match:\n\n```bash\nbundle exec rubocop\nbundle exec rubocop -A\n```\n\n- Do not silence cops inline unless the exception is narrow, documented, and harder to express cleanly in code.\n\n## Rails Style\n\n- Follow Rails naming and directory conventions before adding custom structure.\n- Keep controllers transport-focused: authentication, authorization, parameter handling, response shape.\n- Put reusable domain behavior in models, concerns, service objects, query objects, or form objects based on actual complexity, not as default ceremony.\n- Prefer `bin/rails`, `bin/rake`, and checked-in binstubs over globally installed commands.\n\n## Error Handling\n\n- Rescue specific exceptions. Avoid broad `rescue StandardError` blocks unless they re-raise or preserve enough context for operators.\n- Use `ActiveSupport::Notifications` or the app's logger for operational events; do not leave `puts`, `pp`, or `debugger` in committed application code.\n\n## Reference\n\nSee skill: `backend-patterns` for broader service/repository layering guidance.\n\n### ruby/hooks\n\n# Ruby Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Ruby and Rails specific content.\n\n## PostToolUse Hooks\n\nConfigure project-local hooks to prefer binstubs and checked-in tooling:\n\n- **RuboCop**: run `bundle exec rubocop -A <file>` or the project's safer formatter command after Ruby edits.\n- **Brakeman**: run `bundle exec brakeman --no-progress` after security-sensitive Rails changes.\n- **Tests**: run the narrowest matching `bin/rails test ...` or `bundle exec rspec ...` command for touched files.\n- **Bundler audit**: run `bundle exec bundle-audit check --update` when `Gemfile` or `Gemfile.lock` changes and the project has bundler-audit installed.\n\n## Warnings\n\n- Warn on committed `debugger`, `binding.irb`, `binding.pry`, `puts`, `pp`, or `p` calls in application code.\n- Warn when an edit disables CSRF protection, expands mass-assignment, or adds raw SQL without parameterization.\n- Warn when a migration changes data destructively without a reversible path or documented rollout plan.\n\n## CI Gate Suggestions\n\n```bash\nbundle exec rubocop\nbundle exec brakeman --no-progress\nbin/rails test\nbundle exec rspec\n```\n\nUse only the commands that are present in the project; do not install new hook dependencies without maintainer approval.\n\n### ruby/patterns\n\n# Ruby Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Ruby and Rails specific content.\n\n## Rails Way First\n\n- Start with plain Rails MVC and Active Record conventions for small and medium features.\n- Introduce service objects, query objects, form objects, decorators, or presenters when the model/controller boundary is carrying multiple responsibilities.\n- Name extracted objects after the business operation they perform, not after generic layers like `Manager` or `Processor`.\n\n## Persistence\n\n- Prefer PostgreSQL for multi-host production Rails apps unless the existing platform has a clear reason for MySQL or SQLite.\n- Treat Rails 8 SQLite-backed defaults as viable for single-host or modest deployments, not as an automatic fit for shared multi-service systems.\n- Keep raw SQL behind query objects or model scopes and parameterize every dynamic value.\n\n## Background Jobs And Runtime Services\n\n- Use **Solid Queue** for greenfield Rails 8 apps with modest throughput and simple deployment needs.\n- Use **Sidekiq** when the app needs mature observability, high throughput, existing Redis infrastructure, or Pro/Enterprise features.\n- Use **Solid Cache** and **Solid Cable** when their deployment model matches the app; use Redis when shared cross-service behavior, high fanout, or advanced data structures matter.\n\n## Frontend\n\n- Prefer **Hotwire** with Turbo, Stimulus, Importmap, and Propshaft for server-rendered Rails apps.\n- Use React, Vue, Inertia.js, or a separate SPA when interaction complexity, existing product architecture, or team ownership justifies the extra client surface.\n- Keep view components, partials, and presenters focused on rendering decisions; keep persistence and authorization out of templates.\n\n## Authentication\n\n- Use the Rails 8 authentication generator for straightforward session auth and password reset needs.\n- Use Devise or another established auth system when requirements include OAuth, MFA, confirmable/lockable flows, multi-model auth, or a large existing Devise footprint.\n\n## Reference\n\nSee skill: `backend-patterns` for service boundaries and adapter patterns.\n\n### ruby/security\n\n# Ruby Security\n\n> This file extends [common/security.md](../common/security.md) with Ruby and Rails specific content.\n\n## Rails Defaults\n\n- Keep CSRF protection enabled for state-changing browser requests.\n- Use strong parameters or typed boundary objects before mass assignment.\n- Store secrets in Rails credentials, environment variables, or a secret manager. Never commit plaintext keys, tokens, private credentials, or copied `.env` values.\n\n## SQL And Active Record\n\n- Prefer Active Record query APIs and parameterized SQL.\n- Never interpolate request, cookie, header, job, or webhook values into SQL strings.\n- Scope model callbacks carefully; security-sensitive side effects should be explicit and covered by tests.\n\n## Authentication And Sessions\n\n- Use the Rails 8 authentication generator for simple session auth, or Devise when OAuth, MFA, confirmable, lockable, multi-model auth, or existing Devise conventions are required.\n- Rotate sessions after sign-in and privilege changes.\n- Protect account recovery flows with expiry, single-use tokens, rate limiting, and audit logging.\n\n## Dependencies\n\n- Run dependency checks when the lockfile changes:\n\n```bash\nbundle exec bundle-audit check --update\nbundle exec brakeman --no-progress\n```\n\n- Review new gems for maintainer activity, native extension risk, transitive dependencies, and whether the same behavior can be implemented with Rails core.\n\n## Web Safety\n\n- Escape template output by default. Treat `html_safe`, `raw`, and custom sanitizers as security-sensitive code.\n- Validate file uploads by content type, extension, size, and storage destination.\n- Treat background jobs, webhooks, Action Cable messages, and Turbo Stream inputs as untrusted boundaries.\n\n## Reference\n\nSee skill: `security-review` for secure-by-default review patterns.\n\n### ruby/testing\n\n# Ruby Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Ruby and Rails specific content.\n\n## Framework\n\n- Use **Minitest** when the Rails app follows the default Rails test stack.\n- Use **RSpec** when it is already established in the project or the team has explicit production conventions around it.\n- Do not mix Minitest and RSpec inside the same feature area without a migration reason.\n\n## Test Pyramid\n\n- Put fast domain behavior in model, service, query, policy, and job tests.\n- Use request/controller tests for HTTP contracts, auth behavior, redirects, status codes, and response shapes.\n- Use system tests with Capybara for browser-critical flows only; keep them focused and stable.\n- Cover background jobs with unit tests for behavior and integration tests for queue/enqueue contracts.\n\n## Fixtures And Factories\n\n- Use Rails fixtures when they are the project default and the data graph is small.\n- Use `factory_bot` when scenarios need explicit object construction or complex traits.\n- Keep test data close to the behavior being asserted; avoid global fixtures that hide setup cost.\n\n## Commands\n\nPrefer project-local commands:\n\n```bash\nbin/rails test\nbin/rails test test/models/user_test.rb\nbundle exec rspec\nbundle exec rspec spec/models/user_spec.rb\n```\n\n## Coverage\n\n- Use SimpleCov when coverage is enforced; keep thresholds in CI and avoid gaming branch coverage with low-value tests.\n- Add regression tests for bug fixes before changing production code.\n\n## Reference\n\nSee skill: `tdd-workflow` for the repo-wide RED -> GREEN -> REFACTOR loop.\n\n### rust/coding-style\n\n# Rust Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Rust-specific content.\n\n## Formatting\n\n- **rustfmt** for enforcement — always run `cargo fmt` before committing\n- **clippy** for lints — `cargo clippy -- -D warnings` (treat warnings as errors)\n- 4-space indent (rustfmt default)\n- Max line width: 100 characters (rustfmt default)\n\n## Immutability\n\nRust variables are immutable by default — embrace this:\n\n- Use `let` by default; only use `let mut` when mutation is required\n- Prefer returning new values over mutating in place\n- Use `Cow<'_, T>` when a function may or may not need to allocate\n\n```rust\nuse std::borrow::Cow;\n\n// GOOD — immutable by default, new value returned\nfn normalize(input: &str) -> Cow<'_, str> {\n if input.contains(' ') {\n Cow::Owned(input.replace(' ', \"_\"))\n } else {\n Cow::Borrowed(input)\n }\n}\n\n// BAD — unnecessary mutation\nfn normalize_bad(input: &mut String) {\n *input = input.replace(' ', \"_\");\n}\n```\n\n## Naming\n\nFollow standard Rust conventions:\n- `snake_case` for functions, methods, variables, modules, crates\n- `PascalCase` (UpperCamelCase) for types, traits, enums, type parameters\n- `SCREAMING_SNAKE_CASE` for constants and statics\n- Lifetimes: short lowercase (`'a`, `'de`) — descriptive names for complex cases (`'input`)\n\n## Ownership and Borrowing\n\n- Borrow (`&T`) by default; take ownership only when you need to store or consume\n- Never clone to satisfy the borrow checker without understanding the root cause\n- Accept `&str` over `String`, `&[T]` over `Vec<T>` in function parameters\n- Use `impl Into<String>` for constructors that need to own a `String`\n\n```rust\n// GOOD — borrows when ownership isn't needed\nfn word_count(text: &str) -> usize {\n text.split_whitespace().count()\n}\n\n// GOOD — takes ownership in constructor via Into\nfn new(name: impl Into<String>) -> Self {\n Self { name: name.into() }\n}\n\n// BAD — takes String when &str suffices\nfn word_count_bad(text: String) -> usize {\n text.split_whitespace().count()\n}\n```\n\n## Error Handling\n\n- Use `Result<T, E>` and `?` for propagation — never `unwrap()` in production code\n- **Libraries**: define typed errors with `thiserror`\n- **Applications**: use `anyhow` for flexible error context\n- Add context with `.with_context(|| format!(\"failed to ...\"))?`\n- Reserve `unwrap()` / `expect()` for tests and truly unreachable states\n\n```rust\n// GOOD — library error with thiserror\n#[derive(Debug, thiserror::Error)]\npub enum ConfigError {\n #[error(\"failed to read config: {0}\")]\n Io(#[from] std::io::Error),\n #[error(\"invalid config format: {0}\")]\n Parse(String),\n}\n\n// GOOD — application error with anyhow\nuse anyhow::Context;\n\nfn load_config(path: &str) -> anyhow::Result<Config> {\n let content = std::fs::read_to_string(path)\n .with_context(|| format!(\"failed to read {path}\"))?;\n toml::from_str(&content)\n .with_context(|| format!(\"failed to parse {path}\"))\n}\n```\n\n## Iterators Over Loops\n\nPrefer iterator chains for transformations; use loops for complex control flow:\n\n```rust\n// GOOD — declarative and composable\nlet active_emails: Vec<&str> = users.iter()\n .filter(|u| u.is_active)\n .map(|u| u.email.as_str())\n .collect();\n\n// GOOD — loop for complex logic with early returns\nfor user in &users {\n if let Some(verified) = verify_email(&user.email)? {\n send_welcome(&verified)?;\n }\n}\n```\n\n## Module Organization\n\nOrganize by domain, not by type:\n\n```text\nsrc/\n├── main.rs\n├── lib.rs\n├── auth/ # Domain module\n│ ├── mod.rs\n│ ├── token.rs\n│ └── middleware.rs\n├── orders/ # Domain module\n│ ├── mod.rs\n│ ├── model.rs\n│ └── service.rs\n└── db/ # Infrastructure\n ├── mod.rs\n └── pool.rs\n```\n\n## Visibility\n\n- Default to private; use `pub(crate)` for internal sharing\n- Only mark `pub` what is part of the crate's public API\n- Re-export public API from `lib.rs`\n\n## References\n\nSee skill: `rust-patterns` for comprehensive Rust idioms and patterns.\n\n### rust/hooks\n\n# Rust Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Rust-specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **cargo fmt**: Auto-format `.rs` files after edit\n- **cargo clippy**: Run lint checks after editing Rust files\n- **cargo check**: Verify compilation after changes (faster than `cargo build`)\n\n### rust/patterns\n\n# Rust Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Rust-specific content.\n\n## Repository Pattern with Traits\n\nEncapsulate data access behind a trait:\n\n```rust\npub trait OrderRepository: Send + Sync {\n fn find_by_id(&self, id: u64) -> Result<Option<Order>, StorageError>;\n fn find_all(&self) -> Result<Vec<Order>, StorageError>;\n fn save(&self, order: &Order) -> Result<Order, StorageError>;\n fn delete(&self, id: u64) -> Result<(), StorageError>;\n}\n```\n\nConcrete implementations handle storage details (Postgres, SQLite, in-memory for tests).\n\n## Service Layer\n\nBusiness logic in service structs; inject dependencies via constructor:\n\n```rust\npub struct OrderService {\n repo: Box<dyn OrderRepository>,\n payment: Box<dyn PaymentGateway>,\n}\n\nimpl OrderService {\n pub fn new(repo: Box<dyn OrderRepository>, payment: Box<dyn PaymentGateway>) -> Self {\n Self { repo, payment }\n }\n\n pub fn place_order(&self, request: CreateOrderRequest) -> anyhow::Result<OrderSummary> {\n let order = Order::from(request);\n self.payment.charge(order.total())?;\n let saved = self.repo.save(&order)?;\n Ok(OrderSummary::from(saved))\n }\n}\n```\n\n## Newtype Pattern for Type Safety\n\nPrevent argument mix-ups with distinct wrapper types:\n\n```rust\nstruct UserId(u64);\nstruct OrderId(u64);\n\nfn get_order(user: UserId, order: OrderId) -> anyhow::Result<Order> {\n // Can't accidentally swap user and order IDs at call sites\n todo!()\n}\n```\n\n## Enum State Machines\n\nModel states as enums — make illegal states unrepresentable:\n\n```rust\nenum ConnectionState {\n Disconnected,\n Connecting { attempt: u32 },\n Connected { session_id: String },\n Failed { reason: String, retries: u32 },\n}\n\nfn handle(state: &ConnectionState) {\n match state {\n ConnectionState::Disconnected => connect(),\n ConnectionState::Connecting { attempt } if *attempt > 3 => abort(),\n ConnectionState::Connecting { .. } => wait(),\n ConnectionState::Connected { session_id } => use_session(session_id),\n ConnectionState::Failed { retries, .. } if *retries < 5 => retry(),\n ConnectionState::Failed { reason, .. } => log_failure(reason),\n }\n}\n```\n\nAlways match exhaustively — no wildcard `_` for business-critical enums.\n\n## Builder Pattern\n\nUse for structs with many optional parameters:\n\n```rust\npub struct ServerConfig {\n host: String,\n port: u16,\n max_connections: usize,\n}\n\nimpl ServerConfig {\n pub fn builder(host: impl Into<String>, port: u16) -> ServerConfigBuilder {\n ServerConfigBuilder {\n host: host.into(),\n port,\n max_connections: 100,\n }\n }\n}\n\npub struct ServerConfigBuilder {\n host: String,\n port: u16,\n max_connections: usize,\n}\n\nimpl ServerConfigBuilder {\n pub fn max_connections(mut self, n: usize) -> Self {\n self.max_connections = n;\n self\n }\n\n pub fn build(self) -> ServerConfig {\n ServerConfig {\n host: self.host,\n port: self.port,\n max_connections: self.max_connections,\n }\n }\n}\n```\n\n## Sealed Traits for Extensibility Control\n\nUse a private module to seal a trait, preventing external implementations:\n\n```rust\nmod private {\n pub trait Sealed {}\n}\n\npub trait Format: private::Sealed {\n fn encode(&self, data: &[u8]) -> Vec<u8>;\n}\n\npub struct Json;\nimpl private::Sealed for Json {}\nimpl Format for Json {\n fn encode(&self, data: &[u8]) -> Vec<u8> { todo!() }\n}\n```\n\n## API Response Envelope\n\nConsistent API responses using a generic enum:\n\n```rust\n#[derive(Debug, serde::Serialize)]\n#[serde(tag = \"status\")]\npub enum ApiResponse<T: serde::Serialize> {\n #[serde(rename = \"ok\")]\n Ok { data: T },\n #[serde(rename = \"error\")]\n Error { message: String },\n}\n```\n\n## References\n\nSee skill: `rust-patterns` for comprehensive patterns including ownership, traits, generics, concurrency, and async.\n\n### rust/security\n\n# Rust Security\n\n> This file extends [common/security.md](../common/security.md) with Rust-specific content.\n\n## Secrets Management\n\n- Never hardcode API keys, tokens, or credentials in source code\n- Use environment variables: `std::env::var(\"API_KEY\")`\n- Fail fast if required secrets are missing at startup\n- Keep `.env` files in `.gitignore`\n\n```rust\n// BAD\nconst API_KEY: &str = \"sk-abc123...\";\n\n// GOOD — environment variable with early validation\nfn load_api_key() -> anyhow::Result<String> {\n std::env::var(\"PAYMENT_API_KEY\")\n .context(\"PAYMENT_API_KEY must be set\")\n}\n```\n\n## SQL Injection Prevention\n\n- Always use parameterized queries — never format user input into SQL strings\n- Use query builder or ORM (sqlx, diesel, sea-orm) with bind parameters\n\n```rust\n// BAD — SQL injection via format string\nlet query = format!(\"SELECT * FROM users WHERE name = '{name}'\");\nsqlx::query(&query).fetch_one(&pool).await?;\n\n// GOOD — parameterized query with sqlx\n// Placeholder syntax varies by backend: Postgres: $1 | MySQL: ? | SQLite: $1\nsqlx::query(\"SELECT * FROM users WHERE name = $1\")\n .bind(&name)\n .fetch_one(&pool)\n .await?;\n```\n\n## Input Validation\n\n- Validate all user input at system boundaries before processing\n- Use the type system to enforce invariants (newtype pattern)\n- Parse, don't validate — convert unstructured data to typed structs at the boundary\n- Reject invalid input with clear error messages\n\n```rust\n// Parse, don't validate — invalid states are unrepresentable\npub struct Email(String);\n\nimpl Email {\n pub fn parse(input: &str) -> Result<Self, ValidationError> {\n let trimmed = input.trim();\n let at_pos = trimmed.find('@')\n .filter(|&p| p > 0 && p < trimmed.len() - 1)\n .ok_or_else(|| ValidationError::InvalidEmail(input.to_string()))?;\n let domain = &trimmed[at_pos + 1..];\n if trimmed.len() > 254 || !domain.contains('.') {\n return Err(ValidationError::InvalidEmail(input.to_string()));\n }\n // For production use, prefer a validated email crate (e.g., `email_address`)\n Ok(Self(trimmed.to_string()))\n }\n\n pub fn as_str(&self) -> &str {\n &self.0\n }\n}\n```\n\n## Unsafe Code\n\n- Minimize `unsafe` blocks — prefer safe abstractions\n- Every `unsafe` block must have a `// SAFETY:` comment explaining the invariant\n- Never use `unsafe` to bypass the borrow checker for convenience\n- Audit all `unsafe` code during review — it is a red flag without justification\n- Prefer `safe` FFI wrappers around C libraries\n\n```rust\n// GOOD — safety comment documents ALL required invariants\nlet widget: &Widget = {\n // SAFETY: `ptr` is non-null, aligned, points to an initialized Widget,\n // and no mutable references or mutations exist for its lifetime.\n unsafe { &*ptr }\n};\n\n// BAD — no safety justification\nunsafe { &*ptr }\n```\n\n## Dependency Security\n\n- Run `cargo audit` to scan for known CVEs in dependencies\n- Run `cargo deny check` for license and advisory compliance\n- Use `cargo tree` to audit transitive dependencies\n- Keep dependencies updated — set up Dependabot or Renovate\n- Minimize dependency count — evaluate before adding new crates\n\n```bash\n# Security audit\ncargo audit\n\n# Deny advisories, duplicate versions, and restricted licenses\ncargo deny check\n\n# Inspect dependency tree\ncargo tree\ncargo tree -d # Show duplicates only\n```\n\n## Error Messages\n\n- Never expose internal paths, stack traces, or database errors in API responses\n- Log detailed errors server-side; return generic messages to clients\n- Use `tracing` or `log` for structured server-side logging\n\n```rust\n// Map errors to appropriate status codes and generic messages\n// (Example uses axum; adapt the response type to your framework)\nmatch order_service.find_by_id(id) {\n Ok(order) => Ok((StatusCode::OK, Json(order))),\n Err(ServiceError::NotFound(_)) => {\n tracing::info!(order_id = id, \"order not found\");\n Err((StatusCode::NOT_FOUND, \"Resource not found\"))\n }\n Err(e) => {\n tracing::error!(order_id = id, error = %e, \"unexpected error\");\n Err((StatusCode::INTERNAL_SERVER_ERROR, \"Internal server error\"))\n }\n}\n```\n\n## References\n\nSee skill: `rust-patterns` for unsafe code guidelines and ownership patterns.\nSee skill: `security-review` for general security checklists.\n\n### rust/testing\n\n# Rust Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Rust-specific content.\n\n## Test Framework\n\n- **`#[test]`** with `#[cfg(test)]` modules for unit tests\n- **rstest** for parameterized tests and fixtures\n- **proptest** for property-based testing\n- **mockall** for trait-based mocking\n- **`#[tokio::test]`** for async tests\n\n## Test Organization\n\n```text\nmy_crate/\n├── src/\n│ ├── lib.rs # Unit tests in #[cfg(test)] modules\n│ ├── auth/\n│ │ └── mod.rs # #[cfg(test)] mod tests { ... }\n│ └── orders/\n│ └── service.rs # #[cfg(test)] mod tests { ... }\n├── tests/ # Integration tests (each file = separate binary)\n│ ├── api_test.rs\n│ ├── db_test.rs\n│ └── common/ # Shared test utilities\n│ └── mod.rs\n└── benches/ # Criterion benchmarks\n └── benchmark.rs\n```\n\nUnit tests go inside `#[cfg(test)]` modules in the same file. Integration tests go in `tests/`.\n\n## Unit Test Pattern\n\n```rust\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn creates_user_with_valid_email() {\n let user = User::new(\"Alice\", \"alice@example.com\").unwrap();\n assert_eq!(user.name, \"Alice\");\n }\n\n #[test]\n fn rejects_invalid_email() {\n let result = User::new(\"Bob\", \"not-an-email\");\n assert!(result.is_err());\n assert!(result.unwrap_err().to_string().contains(\"invalid email\"));\n }\n}\n```\n\n## Parameterized Tests\n\n```rust\nuse rstest::rstest;\n\n#[rstest]\n#[case(\"hello\", 5)]\n#[case(\"\", 0)]\n#[case(\"rust\", 4)]\nfn test_string_length(#[case] input: &str, #[case] expected: usize) {\n assert_eq!(input.len(), expected);\n}\n```\n\n## Async Tests\n\n```rust\n#[tokio::test]\nasync fn fetches_data_successfully() {\n let client = TestClient::new().await;\n let result = client.get(\"/data\").await;\n assert!(result.is_ok());\n}\n```\n\n## Mocking with mockall\n\nDefine traits in production code; generate mocks in test modules:\n\n```rust\n// Production trait — pub so integration tests can import it\npub trait UserRepository {\n fn find_by_id(&self, id: u64) -> Option<User>;\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n use mockall::predicate::eq;\n\n mockall::mock! {\n pub Repo {}\n impl UserRepository for Repo {\n fn find_by_id(&self, id: u64) -> Option<User>;\n }\n }\n\n #[test]\n fn service_returns_user_when_found() {\n let mut mock = MockRepo::new();\n mock.expect_find_by_id()\n .with(eq(42))\n .times(1)\n .returning(|_| Some(User { id: 42, name: \"Alice\".into() }));\n\n let service = UserService::new(Box::new(mock));\n let user = service.get_user(42).unwrap();\n assert_eq!(user.name, \"Alice\");\n }\n}\n```\n\n## Test Naming\n\nUse descriptive names that explain the scenario:\n- `creates_user_with_valid_email()`\n- `rejects_order_when_insufficient_stock()`\n- `returns_none_when_not_found()`\n\n## Coverage\n\n- Target 80%+ line coverage\n- Use **cargo-llvm-cov** for coverage reporting\n- Focus on business logic — exclude generated code and FFI bindings\n\n```bash\ncargo llvm-cov # Summary\ncargo llvm-cov --html # HTML report\ncargo llvm-cov --fail-under-lines 80 # Fail if below threshold\n```\n\n## Testing Commands\n\n```bash\ncargo test # Run all tests\ncargo test -- --nocapture # Show println output\ncargo test test_name # Run tests matching pattern\ncargo test --lib # Unit tests only\ncargo test --test api_test # Specific integration test (tests/api_test.rs)\ncargo test --doc # Doc tests only\n```\n\n## References\n\nSee skill: `rust-testing` for comprehensive testing patterns including property-based testing, fixtures, and benchmarking with Criterion.\n\n### swift/coding-style\n\n# Swift Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Swift specific content.\n\n## Formatting\n\n- **SwiftFormat** for auto-formatting, **SwiftLint** for style enforcement\n- `swift-format` is bundled with Xcode 16+ as an alternative\n\n## Immutability\n\n- Prefer `let` over `var` — define everything as `let` and only change to `var` if the compiler requires it\n- Use `struct` with value semantics by default; use `class` only when identity or reference semantics are needed\n\n## Naming\n\nFollow [Apple API Design Guidelines](https://www.swift.org/documentation/api-design-guidelines/):\n\n- Clarity at the point of use — omit needless words\n- Name methods and properties for their roles, not their types\n- Use `static let` for constants over global constants\n\n## Error Handling\n\nUse typed throws (Swift 6+) and pattern matching:\n\n```swift\nfunc load(id: String) throws(LoadError) -> Item {\n guard let data = try? read(from: path) else {\n throw .fileNotFound(id)\n }\n return try decode(data)\n}\n```\n\n## Concurrency\n\nEnable Swift 6 strict concurrency checking. Prefer:\n\n- `Sendable` value types for data crossing isolation boundaries\n- Actors for shared mutable state\n- Structured concurrency (`async let`, `TaskGroup`) over unstructured `Task {}`\n\n### swift/hooks\n\n# Swift Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Swift specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **SwiftFormat**: Auto-format `.swift` files after edit\n- **SwiftLint**: Run lint checks after editing `.swift` files\n- **swift build**: Type-check modified packages after edit\n\n## Warning\n\nFlag `print()` statements — use `os.Logger` or structured logging instead for production code.\n\n### swift/patterns\n\n# Swift Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Swift specific content.\n\n## Protocol-Oriented Design\n\nDefine small, focused protocols. Use protocol extensions for shared defaults:\n\n```swift\nprotocol Repository: Sendable {\n associatedtype Item: Identifiable & Sendable\n func find(by id: Item.ID) async throws -> Item?\n func save(_ item: Item) async throws\n}\n```\n\n## Value Types\n\n- Use structs for data transfer objects and models\n- Use enums with associated values to model distinct states:\n\n```swift\nenum LoadState<T: Sendable>: Sendable {\n case idle\n case loading\n case loaded(T)\n case failed(Error)\n}\n```\n\n## Actor Pattern\n\nUse actors for shared mutable state instead of locks or dispatch queues:\n\n```swift\nactor Cache<Key: Hashable & Sendable, Value: Sendable> {\n private var storage: [Key: Value] = [:]\n\n func get(_ key: Key) -> Value? { storage[key] }\n func set(_ key: Key, value: Value) { storage[key] = value }\n}\n```\n\n## Dependency Injection\n\nInject protocols with default parameters — production uses defaults, tests inject mocks:\n\n```swift\nstruct UserService {\n private let repository: any UserRepository\n\n init(repository: any UserRepository = DefaultUserRepository()) {\n self.repository = repository\n }\n}\n```\n\n## References\n\nSee skill: `swift-actor-persistence` for actor-based persistence patterns.\nSee skill: `swift-protocol-di-testing` for protocol-based DI and testing.\n\n### swift/security\n\n# Swift Security\n\n> This file extends [common/security.md](../common/security.md) with Swift specific content.\n\n## Secret Management\n\n- Use **Keychain Services** for sensitive data (tokens, passwords, keys) — never `UserDefaults`\n- Use environment variables or `.xcconfig` files for build-time secrets\n- Never hardcode secrets in source — decompilation tools extract them trivially\n\n```swift\nlet apiKey = ProcessInfo.processInfo.environment[\"API_KEY\"]\nguard let apiKey, !apiKey.isEmpty else {\n fatalError(\"API_KEY not configured\")\n}\n```\n\n## Transport Security\n\n- App Transport Security (ATS) is enforced by default — do not disable it\n- Use certificate pinning for critical endpoints\n- Validate all server certificates\n\n## Input Validation\n\n- Sanitize all user input before display to prevent injection\n- Use `URL(string:)` with validation rather than force-unwrapping\n- Validate data from external sources (APIs, deep links, pasteboard) before processing\n\n### swift/testing\n\n# Swift Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Swift specific content.\n\n## Framework\n\nUse **Swift Testing** (`import Testing`) for new tests. Use `@Test` and `#expect`:\n\n```swift\n@Test(\"User creation validates email\")\nfunc userCreationValidatesEmail() throws {\n #expect(throws: ValidationError.invalidEmail) {\n try User(email: \"not-an-email\")\n }\n}\n```\n\n## Test Isolation\n\nEach test gets a fresh instance — set up in `init`, tear down in `deinit`. No shared mutable state between tests.\n\n## Parameterized Tests\n\n```swift\n@Test(\"Validates formats\", arguments: [\"json\", \"xml\", \"csv\"])\nfunc validatesFormat(format: String) throws {\n let parser = try Parser(format: format)\n #expect(parser.isValid)\n}\n```\n\n## Coverage\n\n```bash\nswift test --enable-code-coverage\n```\n\n## Reference\n\nSee skill: `swift-protocol-di-testing` for protocol-based dependency injection and mock patterns with Swift Testing.\n\n### typescript/coding-style\n\n# TypeScript/JavaScript Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with TypeScript/JavaScript specific content.\n\n## Types and Interfaces\n\nUse types to make public APIs, shared models, and component props explicit, readable, and reusable.\n\n### Public APIs\n\n- Add parameter and return types to exported functions, shared utilities, and public class methods\n- Let TypeScript infer obvious local variable types\n- Extract repeated inline object shapes into named types or interfaces\n\n```typescript\n// WRONG: Exported function without explicit types\nexport function formatUser(user) {\n return `${user.firstName} ${user.lastName}`\n}\n\n// CORRECT: Explicit types on public APIs\ninterface User {\n firstName: string\n lastName: string\n}\n\nexport function formatUser(user: User): string {\n return `${user.firstName} ${user.lastName}`\n}\n```\n\n### Interfaces vs. Type Aliases\n\n- Use `interface` for object shapes that may be extended or implemented\n- Use `type` for unions, intersections, tuples, mapped types, and utility types\n- Prefer string literal unions over `enum` unless an `enum` is required for interoperability\n\n```typescript\ninterface User {\n id: string\n email: string\n}\n\ntype UserRole = 'admin' | 'member'\ntype UserWithRole = User & {\n role: UserRole\n}\n```\n\n### Avoid `any`\n\n- Avoid `any` in application code\n- Use `unknown` for external or untrusted input, then narrow it safely\n- Use generics when a value's type depends on the caller\n\n```typescript\n// WRONG: any removes type safety\nfunction getErrorMessage(error: any) {\n return error.message\n}\n\n// CORRECT: unknown forces safe narrowing\nfunction getErrorMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message\n }\n\n return 'Unexpected error'\n}\n```\n\n### React Props\n\n- Define component props with a named `interface` or `type`\n- Type callback props explicitly\n- Do not use `React.FC` unless there is a specific reason to do so\n\n```typescript\ninterface User {\n id: string\n email: string\n}\n\ninterface UserCardProps {\n user: User\n onSelect: (id: string) => void\n}\n\nfunction UserCard({ user, onSelect }: UserCardProps) {\n return <button onClick={() => onSelect(user.id)}>{user.email}</button>\n}\n```\n\n### JavaScript Files\n\n- In `.js` and `.jsx` files, use JSDoc when types improve clarity and a TypeScript migration is not practical\n- Keep JSDoc aligned with runtime behavior\n\n```javascript\n/**\n * @param {{ firstName: string, lastName: string }} user\n * @returns {string}\n */\nexport function formatUser(user) {\n return `${user.firstName} ${user.lastName}`\n}\n```\n\n## Immutability\n\nUse spread operator for immutable updates:\n\n```typescript\ninterface User {\n id: string\n name: string\n}\n\n// WRONG: Mutation\nfunction updateUser(user: User, name: string): User {\n user.name = name // MUTATION!\n return user\n}\n\n// CORRECT: Immutability\nfunction updateUser(user: Readonly<User>, name: string): User {\n return {\n ...user,\n name\n }\n}\n```\n\n## Error Handling\n\nUse async/await with try-catch and narrow unknown errors safely:\n\n```typescript\ninterface User {\n id: string\n email: string\n}\n\ndeclare function riskyOperation(userId: string): Promise<User>\n\nfunction getErrorMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message\n }\n\n return 'Unexpected error'\n}\n\nconst logger = {\n error: (message: string, error: unknown) => {\n // Replace with your production logger (for example, pino or winston).\n }\n}\n\nasync function loadUser(userId: string): Promise<User> {\n try {\n const result = await riskyOperation(userId)\n return result\n } catch (error: unknown) {\n logger.error('Operation failed', error)\n throw new Error(getErrorMessage(error))\n }\n}\n```\n\n## Input Validation\n\nUse Zod for schema-based validation and infer types from the schema:\n\n```typescript\nimport { z } from 'zod'\n\nconst userSchema = z.object({\n email: z.string().email(),\n age: z.number().int().min(0).max(150)\n})\n\ntype UserInput = z.infer<typeof userSchema>\n\nconst validated: UserInput = userSchema.parse(input)\n```\n\n## Console.log\n\n- No `console.log` statements in production code\n- Use proper logging libraries instead\n- See hooks for automatic detection\n\n### typescript/hooks\n\n# TypeScript/JavaScript Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with TypeScript/JavaScript specific content.\n\n## PostToolUse Hooks\n\nConfigure in `~/.claude/settings.json`:\n\n- **Prettier**: Auto-format JS/TS files after edit\n- **TypeScript check**: Run `tsc` after editing `.ts`/`.tsx` files\n- **console.log warning**: Warn about `console.log` in edited files\n\n## Stop Hooks\n\n- **console.log audit**: Check all modified files for `console.log` before session ends\n\n### typescript/patterns\n\n# TypeScript/JavaScript Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with TypeScript/JavaScript specific content.\n\n## API Response Format\n\n```typescript\ninterface ApiResponse<T> {\n success: boolean\n data?: T\n error?: string\n meta?: {\n total: number\n page: number\n limit: number\n }\n}\n```\n\n## Custom Hooks Pattern\n\n```typescript\nexport function useDebounce<T>(value: T, delay: number): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value)\n\n useEffect(() => {\n const handler = setTimeout(() => setDebouncedValue(value), delay)\n return () => clearTimeout(handler)\n }, [value, delay])\n\n return debouncedValue\n}\n```\n\n## Repository Pattern\n\n```typescript\ninterface Repository<T> {\n findAll(filters?: Filters): Promise<T[]>\n findById(id: string): Promise<T | null>\n create(data: CreateDto): Promise<T>\n update(id: string, data: UpdateDto): Promise<T>\n delete(id: string): Promise<void>\n}\n```\n\n### typescript/security\n\n# TypeScript/JavaScript Security\n\n> This file extends [common/security.md](../common/security.md) with TypeScript/JavaScript specific content.\n\n## Secret Management\n\n```typescript\n// NEVER: Hardcoded secrets\nconst apiKey = \"sk-proj-xxxxx\"\n\n// ALWAYS: Environment variables\nconst apiKey = process.env.API_KEY\n\nif (!apiKey) {\n throw new Error('API_KEY not configured')\n}\n```\n\n## Agent Support\n\n- Use **security-reviewer** skill for comprehensive security audits\n\n### typescript/testing\n\n# TypeScript/JavaScript Testing\n\n> This file extends [common/testing.md](../common/testing.md) with TypeScript/JavaScript specific content.\n\n## E2E Testing\n\nUse **Playwright** as the E2E testing framework for critical user flows.\n\n## Agent Support\n\n- **e2e-runner** - Playwright E2E testing specialist\n\n### vue/coding-style\n\n# Vue Coding Style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with Vue specific content.\n\n## SFC Structure\n\n- Always `<script setup lang=\"ts\">` with the Composition API. No Options API in new code.\n- Block order inside a `.vue` file: `<script setup>`, then `<template>`, then `<style scoped>`. One component per file.\n- Naming: component files PascalCase (`AuctionCard.vue`), composables camelCase prefixed `useXxx` (`useAuctionTimer`).\n- Format with Prettier plus ESLint flat config using `eslint-plugin-vue` (`vue/vue3-recommended`). Type-check with `vue-tsc`.\n\n## Reactivity Discipline\n\n- `ref` is the primary state API. Mutate via `.value` in script, auto-unwrapped only at template top level.\n- Nested `ref` inside arrays, `Map`, or `Set` still needs `.value` to read.\n- Reach for `reactive` only for grouped object state. Never reassign a whole `reactive` object.\n- Never destructure a `reactive` object or a Pinia store without `toRefs` / `storeToRefs`. Plain destructure silently drops reactivity.\n\n## Computed and Watchers\n\n- `computed` getters must be pure: no side effects, no async, no DOM access.\n- 3.4+ `computed` only triggers when the returned value changes. Return the prior object unchanged when equal to skip downstream updates.\n- `watch` is lazy. Pass a getter for a reactive property (`watch(() => x.value, ...)`), not the bare reactive object.\n- `watchEffect` is eager and stops tracking dependencies after its first `await`.\n\n## Lifecycle and DOM\n\n- Register lifecycle hooks synchronously inside `setup` (`onMounted`, `onUnmounted`).\n- Clean up timers, listeners, and subscriptions in `onUnmounted`.\n- Read or measure the DOM only after `await nextTick()`.\n\n## Macros and Templates\n\n- Macros: `defineProps` / `defineEmits` (tuple form `change: [id: number]`), `defineModel` (3.4+) for `v-model`, `withDefaults` or 3.5+ reactive-props-destructure for defaults, `defineExpose` for the public ref API.\n- Put a `:key` on every `v-for`, a stable unique primitive. Never the array index, never an object.\n- Never put `v-if` and `v-for` on the same element. Wrap with `<template v-for>` plus an inner `v-if`, or precompute a filtered list.\n\n```vue\n<script setup lang=\"ts\">\nconst props = defineProps<{ id: number }>()\nconst emit = defineEmits<{ change: [id: number] }>()\nconst open = defineModel<boolean>('open', { default: false })\n</script>\n```\n\n## Reference\n\n- ECC skills: `frontend-patterns`, `vite-patterns`.\n- Docs: <https://vuejs.org/api/sfc-script-setup.html> · <https://vuejs.org/guide/essentials/reactivity-fundamentals.html> · <https://eslint.vuejs.org/>\n\n### vue/hooks\n\n# Vue Hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with Vue specific content.\n\n## PostToolUse Targets\n\nRun on `*.vue`, `*.ts`, and `*.tsx` after edits. Scope to changed files where possible.\n\n## Typecheck\n\n- Use `vue-tsc --noEmit` for SFC plus TypeScript checking. Plain `tsc` cannot read `.vue` single-file components, so it must not be the typecheck hook for this project.\n- Typecheck is project-wide. Debounce or scope it so a save-on-every-keystroke loop does not stall the editor.\n\n## Lint and Format\n\n- `eslint --fix` with `eslint-plugin-vue` (flat-config `vue/vue3-recommended`) covers both template and script lint.\n- `prettier --write` for formatting. Prefer Prettier-via-ESLint over a separate Prettier pass to avoid double formatting and fight loops.\n\n## Architecture Boundaries\n\n- Optional: enforce Feature-Sliced Design slice boundaries with `@feature-sliced/steiger` or `eslint-plugin-boundaries` to block deep cross-slice imports.\n\n## Sequencing\n\n```bash\n# changed files only\neslint --fix \"$FILE\"\nprettier --write \"$FILE\"\n# project-wide, debounced\nvue-tsc --noEmit\n```\n\n- Run lint and format per-file first, then the project-wide typecheck last so type errors reflect the formatted source.\n\n## Reference\n\n- ECC skills: `frontend-patterns`, `vite-patterns`.\n- Docs: <https://github.com/vuejs/language-tools> (vue-tsc) · <https://eslint.vuejs.org/> · <https://github.com/feature-sliced/steiger>\n\n### vue/patterns\n\n# Vue Patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with Vue specific content.\n\n## Composables\n\n- The composable (`useXxx`) is the reusable-logic unit. In Feature-Sliced Design it lives in the slice `model` segment.\n- Accept `MaybeRefOrGetter<T>` inputs and normalize with `toValue`, so callers can pass a ref, a getter, or a raw value.\n- Return `toRefs(reactive(...))` so consumers can destructure without losing reactivity.\n- A composable that uses lifecycle hooks or `provide` / `inject` must be called inside a component `setup`, not lazily or conditionally.\n\n## Props, Emits, v-model\n\n- Type-based `defineProps<Props>()` and tuple-form `defineEmits<{ change: [id: number] }>()`.\n- `defineModel<T>('name', { default })` for two-way binding. It compiles to a prop plus an `update:*` emit.\n\n## Provide / Inject\n\n- Use `provide` / `inject` for tree-scoped data without prop drilling.\n- Type-safe collision-free keys: `const key = Symbol() as InjectionKey<T>`.\n- The provider owns mutations. Expose a `readonly` ref plus an explicit updater function, never a raw mutable ref.\n\n## Pinia (FSD model segment)\n\n- Prefer setup stores: `ref` is state, `computed` is getters, `function` is actions.\n- Setup stores do not get `$reset` for free. Define your own.\n- Use `storeToRefs` for state and getters. Destructure actions directly off the store.\n- Never persist raw auth tokens to `localStorage`.\n\n## vue-router\n\n- Lazy-load route components with dynamic `import()`.\n- A global `beforeEach` auth gate keyed on `meta.requiresAuth`. Guards return `false` (cancel), a route location (redirect), or `undefined` / `true` (continue).\n- Watch `() => route.params.id`, not the whole `route` object.\n\n## vue-query (server cache)\n\n- `@tanstack/vue-query` owns server-cache state. Pinia owns client state.\n- Put request functions plus `queryOptions` factories in the FSD `api` segment.\n- Critical: put the ref or computed ITSELF in the query key, never `.value`. Passing `.value` freezes the key and kills reactive refetch.\n\n```ts\nuseQuery({ queryKey: ['auction', id], queryFn: () => fetchAuction(toValue(id)) })\n// after a mutation\nqueryClient.invalidateQueries({ queryKey: ['auction', id] })\n```\n\n## Reference\n\n- ECC skills: `frontend-patterns`, `vite-patterns`.\n- Docs: <https://pinia.vuejs.org/> · <https://router.vuejs.org/> · <https://tanstack.com/query/latest/docs/framework/vue/overview> · <https://vuejs.org/guide/reusability/composables.html>\n\n### vue/security\n\n# Vue Security\n\n> This file extends [common/security.md](../common/security.md) with Vue specific content.\n\n## What Vue Escapes Automatically\n\n- Text interpolation `{{ }}` and dynamic attribute bindings (`:title`) are auto-escaped. The vectors below are NOT protected.\n\n## Rule No.1: Templates from Trusted Sources Only\n\n- Never use non-trusted content as a component template. No runtime template compilation from user input.\n- No user-controlled `:is` that resolves a component from an arbitrary string.\n\n## v-html and Render Functions\n\n- `v-html` bypasses escaping and is a direct XSS vector. Avoid it on user content.\n- If unavoidable, sanitize with DOMPurify (allowlist config) before binding, or render in a sandboxed iframe. Vue itself recommends sanitizing on the backend before persisting.\n- Render-function and scoped-slot output carry the same risk. Passing user HTML through `h()` with `innerHTML` is `v-html` by another name. Sanitize first.\n\n## URL, Style, and Event Injection\n\n- `:href` and `:src` are not escaped. `javascript:` URLs execute. Validate the scheme, allow `http` / `https` / `mailto` only. Vue docs reference `@braintree/sanitize-url`, but sanitize on the backend before persisting.\n- `:style` with user input is unsafe (CSS exfiltration). Use object syntax with whitelisted properties, never a raw user string.\n- Never bind user input to `onclick`, `onfocus`, or any event attribute.\n\n## Client Bundle Secrets\n\n- Anything in `import.meta.env.VITE_*` ships to the browser. Keep API keys and tokens server-side.\n- Use httpOnly cookies for session tokens. Never bundle credentials into the client.\n\n```vue\n<!-- unsafe -->\n<div v-html=\"userBio\" />\n<!-- safe -->\n<div v-html=\"sanitize(userBio)\" />\n```\n\n## Reference\n\n- ECC skills: `frontend-patterns`, `vite-patterns`.\n- Docs: <https://vuejs.org/guide/best-practices/security.html> · <https://github.com/cure53/DOMPurify> · <https://github.com/braintree/sanitize-url>\n\n### vue/testing\n\n# Vue Testing\n\n> This file extends [common/testing.md](../common/testing.md) with Vue specific content.\n\n## Stack\n\n- Vitest (Vite-native runner) plus `@vue/test-utils`. `create-vue` scaffolds `@vitejs/plugin-vue`.\n- DOM environment via `happy-dom` or `jsdom`, set in `vite.config.ts` under `test.environment`.\n\n## Rendering and Async\n\n- `mount` for a full render. `shallowMount` to stub all child components.\n- `trigger` and `setValue` return promises, `await` them.\n- `flushPromises` flushes resolved promise handlers. `nextTick` settles the DOM after a state change.\n\n## What to Test\n\n- Test the public interface only: props, emitted events, slots, rendered output.\n- Do not assert private state or internal methods, and do not rely solely on snapshots.\n\n## Composables\n\n- Composables that use only reactivity APIs unit-test directly: call the function, assert on the returned refs.\n- Composables that use lifecycle hooks or `inject` must be tested through a host component.\n\n## Pinia\n\n- In components: `createTestingPinia()` from `@pinia/testing`, passed via `global.plugins`. Actions are stubbed by default, set `stubActions: false` to run them. `createSpy: vi.fn` is required under Vitest (no Jest globals).\n- In isolation: `beforeEach(() => setActivePinia(createPinia()))` gives a fresh store per test and prevents state leakage.\n\n## Mount Config\n\n- `global.plugins`, `global.stubs` (stubs `Transition` / `TransitionGroup` by default), `global.mocks` (e.g. `$router`), `global.provide` (for `inject`, Symbol keys supported).\n- `RouterLinkStub` stubs `router-link` without mounting a full router.\n\n```ts\nconst wrapper = mount(AuctionCard, {\n props: { id: 1 },\n global: { plugins: [createTestingPinia({ createSpy: vi.fn })] },\n})\nawait wrapper.find('button').trigger('click')\nexpect(wrapper.emitted('bid')).toBeTruthy()\n```\n\n## Reference\n\n- ECC skills: `frontend-patterns`, `vite-patterns`.\n- Docs: <https://test-utils.vuejs.org/api/> · <https://pinia.vuejs.org/cookbook/testing.html> · <https://vitest.dev/>\n\n### web/coding-style\n\n> This file extends [common/coding-style.md](../common/coding-style.md) with web-specific frontend content.\n\n# Web Coding Style\n\n## File Organization\n\nOrganize by feature or surface area, not by file type:\n\n```text\nsrc/\n├── components/\n│ ├── hero/\n│ │ ├── Hero.tsx\n│ │ ├── HeroVisual.tsx\n│ │ └── hero.css\n│ ├── scrolly-section/\n│ │ ├── ScrollySection.tsx\n│ │ ├── StickyVisual.tsx\n│ │ └── scrolly.css\n│ └── ui/\n│ ├── Button.tsx\n│ ├── SurfaceCard.tsx\n│ └── AnimatedText.tsx\n├── hooks/\n│ ├── useReducedMotion.ts\n│ └── useScrollProgress.ts\n├── lib/\n│ ├── animation.ts\n│ └── color.ts\n└── styles/\n ├── tokens.css\n ├── typography.css\n └── global.css\n```\n\n## CSS Custom Properties\n\nDefine design tokens as variables. Do not hardcode palette, typography, or spacing repeatedly:\n\n```css\n:root {\n --color-surface: oklch(98% 0 0);\n --color-text: oklch(18% 0 0);\n --color-accent: oklch(68% 0.21 250);\n\n --text-base: clamp(1rem, 0.92rem + 0.4vw, 1.125rem);\n --text-hero: clamp(3rem, 1rem + 7vw, 8rem);\n\n --space-section: clamp(4rem, 3rem + 5vw, 10rem);\n\n --duration-fast: 150ms;\n --duration-normal: 300ms;\n --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);\n}\n```\n\n## Animation-Only Properties\n\nPrefer compositor-friendly motion:\n- `transform`\n- `opacity`\n- `clip-path`\n- `filter` (sparingly)\n\nAvoid animating layout-bound properties:\n- `width`\n- `height`\n- `top`\n- `left`\n- `margin`\n- `padding`\n- `border`\n- `font-size`\n\n## Semantic HTML First\n\n```html\n<header>\n <nav aria-label=\"Main navigation\">...</nav>\n</header>\n<main>\n <section aria-labelledby=\"hero-heading\">\n <h1 id=\"hero-heading\">...</h1>\n </section>\n</main>\n<footer>...</footer>\n```\n\nDo not reach for generic wrapper `div` stacks when a semantic element exists.\n\n## Naming\n\n- Components: PascalCase (`ScrollySection`, `SurfaceCard`)\n- Hooks: `use` prefix (`useReducedMotion`)\n- CSS classes: kebab-case or utility classes\n- Animation timelines: camelCase with intent (`heroRevealTl`)\n\n### web/design-quality\n\n> This file extends [common/patterns.md](../common/patterns.md) with web-specific design-quality guidance.\n\n# Web Design Quality Standards\n\n## Anti-Template Policy\n\nDo not ship generic template-looking UI. Frontend output should look intentional, opinionated, and specific to the product.\n\n### Banned Patterns\n\n- Default card grids with uniform spacing and no hierarchy\n- Stock hero section with centered headline, gradient blob, and generic CTA\n- Unmodified library defaults passed off as finished design\n- Flat layouts with no layering, depth, or motion\n- Uniform radius, spacing, and shadows across every component\n- Safe gray-on-white styling with one decorative accent color\n- Dashboard-by-numbers layouts with sidebar + cards + charts and no point of view\n- Default font stacks used without a deliberate reason\n\n### Required Qualities\n\nEvery meaningful frontend surface should demonstrate at least four of these:\n\n1. Clear hierarchy through scale contrast\n2. Intentional rhythm in spacing, not uniform padding everywhere\n3. Depth or layering through overlap, shadows, surfaces, or motion\n4. Typography with character and a real pairing strategy\n5. Color used semantically, not just decoratively\n6. Hover, focus, and active states that feel designed\n7. Grid-breaking editorial or bento composition where appropriate\n8. Texture, grain, or atmosphere when it fits the visual direction\n9. Motion that clarifies flow instead of distracting from it\n10. Data visualization treated as part of the design system, not an afterthought\n\n## Before Writing Frontend Code\n\n1. Pick a specific style direction. Avoid vague defaults like \"clean minimal\".\n2. Define a palette intentionally.\n3. Choose typography deliberately.\n4. Gather at least a small set of real references.\n5. Use ECC design/frontend skills where relevant.\n\n## Worthwhile Style Directions\n\n- Editorial / magazine\n- Neo-brutalism\n- Glassmorphism with real depth\n- Dark luxury or light luxury with disciplined contrast\n- Bento layouts\n- Scrollytelling\n- 3D integration\n- Swiss / International\n- Retro-futurism\n\nDo not default to dark mode automatically. Choose the visual direction the product actually wants.\n\n## Component Checklist\n\n- [ ] Does it avoid looking like a default Tailwind or shadcn template?\n- [ ] Does it have intentional hover/focus/active states?\n- [ ] Does it use hierarchy rather than uniform emphasis?\n- [ ] Would this look believable in a real product screenshot?\n- [ ] If it supports both themes, do both light and dark feel intentional?\n\n### web/hooks\n\n> This file extends [common/hooks.md](../common/hooks.md) with web-specific hook recommendations.\n\n# Web Hooks\n\n## Recommended PostToolUse Hooks\n\nPrefer project-local tooling. Do not wire hooks to remote one-off package execution.\n\n### Format on Save\n\nUse the project's existing formatter entrypoint after edits:\n\n```json\n{\n \"hooks\": {\n \"PostToolUse\": [\n {\n \"matcher\": \"Write|Edit\",\n \"command\": \"pnpm prettier --write \\\"$FILE_PATH\\\"\",\n \"description\": \"Format edited frontend files\"\n }\n ]\n }\n}\n```\n\nEquivalent local commands via `yarn prettier` or `npm exec prettier --` are fine when they use repo-owned dependencies.\n\n### Lint Check\n\n```json\n{\n \"hooks\": {\n \"PostToolUse\": [\n {\n \"matcher\": \"Write|Edit\",\n \"command\": \"pnpm eslint --fix \\\"$FILE_PATH\\\"\",\n \"description\": \"Run ESLint on edited frontend files\"\n }\n ]\n }\n}\n```\n\n### Type Check\n\nUse `--incremental` so re-runs reuse the previous `.tsbuildinfo` (1-3s on unchanged code instead of 30-60s every time). Wrap in `timeout` so a stuck tsc gets reaped by the OS instead of accumulating across edits — this prevents the multi-process buildup that happens when edits fire faster than tsc finishes.\n\n```json\n{\n \"hooks\": {\n \"PostToolUse\": [\n {\n \"matcher\": \"Write|Edit\",\n \"command\": \"timeout 60 pnpm tsc --noEmit --pretty false --incremental --tsBuildInfoFile node_modules/.cache/tsc-hook.tsbuildinfo\",\n \"description\": \"Type-check after frontend edits (incremental + timeout-capped)\"\n }\n ]\n }\n}\n```\n\n**Why both flags matter:**\n- Without `--incremental`, every edit re-checks the entire program from scratch. On a real Next.js project this stacks fast: edits at 5-10s intervals + 30-60s tsc runs = N concurrent tsc processes.\n- Without `timeout`, a tsc that hangs (transitive dep change, type-checker stuck on a recursive type) never exits and orphans when the parent shell does.\n- `--tsBuildInfoFile` is required because `--noEmit` normally suppresses the buildinfo write; specifying the path explicitly keeps incremental working.\n\nIf you're on Windows without GNU coreutils, swap `timeout 60` for a PowerShell wrapper or rely on a Stop/SessionEnd hook to sweep stale tsc processes.\n\n### CSS Lint\n\n```json\n{\n \"hooks\": {\n \"PostToolUse\": [\n {\n \"matcher\": \"Write|Edit\",\n \"command\": \"pnpm stylelint --fix \\\"$FILE_PATH\\\"\",\n \"description\": \"Lint edited stylesheets\"\n }\n ]\n }\n}\n```\n\n## PreToolUse Hooks\n\n### Guard File Size\n\nBlock oversized writes from tool input content, not from a file that may not exist yet:\n\n```json\n{\n \"hooks\": {\n \"PreToolUse\": [\n {\n \"matcher\": \"Write\",\n \"command\": \"node -e \\\"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const c=i.tool_input?.content||'';const lines=c.split('\\\\n').length;if(lines>800){console.error('[Hook] BLOCKED: File exceeds 800 lines ('+lines+' lines)');console.error('[Hook] Split into smaller modules');process.exit(2)}console.log(d)})\\\"\",\n \"description\": \"Block writes that exceed 800 lines\"\n }\n ]\n }\n}\n```\n\n## Stop Hooks\n\n### Final Build Verification\n\n```json\n{\n \"hooks\": {\n \"Stop\": [\n {\n \"command\": \"pnpm build\",\n \"description\": \"Verify the production build at session end\"\n }\n ]\n }\n}\n```\n\n## Ordering\n\nRecommended order:\n1. format\n2. lint\n3. type check\n4. build verification\n\n### web/patterns\n\n> This file extends [common/patterns.md](../common/patterns.md) with web-specific patterns.\n\n# Web Patterns\n\n## Component Composition\n\n### Compound Components\n\nUse compound components when related UI shares state and interaction semantics:\n\n```tsx\n<Tabs defaultValue=\"overview\">\n <Tabs.List>\n <Tabs.Trigger value=\"overview\">Overview</Tabs.Trigger>\n <Tabs.Trigger value=\"settings\">Settings</Tabs.Trigger>\n </Tabs.List>\n <Tabs.Content value=\"overview\">...</Tabs.Content>\n <Tabs.Content value=\"settings\">...</Tabs.Content>\n</Tabs>\n```\n\n- Parent owns state\n- Children consume via context\n- Prefer this over prop drilling for complex widgets\n\n### Render Props / Slots\n\n- Use render props or slot patterns when behavior is shared but markup must vary\n- Keep keyboard handling, ARIA, and focus logic in the headless layer\n\n### Container / Presentational Split\n\n- Container components own data loading and side effects\n- Presentational components receive props and render UI\n- Presentational components should stay pure\n\n## State Management\n\nTreat these separately:\n\n| Concern | Tooling |\n|---------|---------|\n| Server state | TanStack Query, SWR, tRPC |\n| Client state | Zustand, Jotai, signals |\n| URL state | search params, route segments |\n| Form state | React Hook Form or equivalent |\n\n- Do not duplicate server state into client stores\n- Derive values instead of storing redundant computed state\n\n## URL As State\n\nPersist shareable state in the URL:\n- filters\n- sort order\n- pagination\n- active tab\n- search query\n\n## Data Fetching\n\n### Stale-While-Revalidate\n\n- Return cached data immediately\n- Revalidate in the background\n- Prefer existing libraries instead of rolling this by hand\n\n### Optimistic Updates\n\n- Snapshot current state\n- Apply optimistic update\n- Roll back on failure\n- Emit visible error feedback when rolling back\n\n### Parallel Loading\n\n- Fetch independent data in parallel\n- Avoid parent-child request waterfalls\n- Prefetch likely next routes or states when justified\n\n### web/performance\n\n> This file extends [common/performance.md](../common/performance.md) with web-specific performance content.\n\n# Web Performance Rules\n\n## Core Web Vitals Targets\n\n| Metric | Target |\n|--------|--------|\n| LCP | < 2.5s |\n| INP | < 200ms |\n| CLS | < 0.1 |\n| FCP | < 1.5s |\n| TBT | < 200ms |\n\n## Bundle Budget\n\n| Page Type | JS Budget (gzipped) | CSS Budget |\n|-----------|---------------------|------------|\n| Landing page | < 150kb | < 30kb |\n| App page | < 300kb | < 50kb |\n| Microsite | < 80kb | < 15kb |\n\n## Loading Strategy\n\n1. Inline critical above-the-fold CSS where justified\n2. Preload the hero image and primary font only\n3. Defer non-critical CSS or JS\n4. Dynamically import heavy libraries\n\n```js\nconst gsapModule = await import('gsap');\nconst { ScrollTrigger } = await import('gsap/ScrollTrigger');\n```\n\n## Image Optimization\n\n- Explicit `width` and `height`\n- `loading=\"eager\"` plus `fetchpriority=\"high\"` for hero media only\n- `loading=\"lazy\"` for below-the-fold assets\n- Prefer AVIF or WebP with fallbacks\n- Never ship source images far beyond rendered size\n\n## Font Loading\n\n- Max two font families unless there is a clear exception\n- `font-display: swap`\n- Subset where possible\n- Preload only the truly critical weight/style\n\n## Animation Performance\n\n- Animate compositor-friendly properties only\n- Use `will-change` narrowly and remove it when done\n- Prefer CSS for simple transitions\n- Use `requestAnimationFrame` or established animation libraries for JS motion\n- Avoid scroll handler churn; use IntersectionObserver or well-behaved libraries\n\n## Performance Checklist\n\n- [ ] All images have explicit dimensions\n- [ ] No accidental render-blocking resources\n- [ ] No layout shifts from dynamic content\n- [ ] Motion stays on compositor-friendly properties\n- [ ] Third-party scripts load async/defer and only when needed\n\n### web/security\n\n> This file extends [common/security.md](../common/security.md) with web-specific security content.\n\n# Web Security Rules\n\n## Content Security Policy\n\nAlways configure a production CSP.\n\n### Nonce-Based CSP\n\nUse a per-request nonce for scripts instead of `'unsafe-inline'`.\n\n```text\nContent-Security-Policy:\n default-src 'self';\n script-src 'self' 'nonce-{RANDOM}' https://cdn.jsdelivr.net;\n style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;\n img-src 'self' data: https:;\n font-src 'self' https://fonts.gstatic.com;\n connect-src 'self' https://*.example.com;\n frame-src 'none';\n object-src 'none';\n base-uri 'self';\n```\n\nAdjust origins to the project. Do not cargo-cult this block unchanged.\n\n## XSS Prevention\n\n- Never inject unsanitized HTML\n- Avoid `innerHTML` / `dangerouslySetInnerHTML` unless sanitized first\n- Escape dynamic template values\n- Sanitize user HTML with a vetted local sanitizer when absolutely necessary\n\n## Third-Party Scripts\n\n- Load asynchronously\n- Use SRI when serving from a CDN\n- Audit quarterly\n- Prefer self-hosting for critical dependencies when practical\n\n## HTTPS and Headers\n\n```text\nStrict-Transport-Security: max-age=31536000; includeSubDomains; preload\nX-Content-Type-Options: nosniff\nX-Frame-Options: DENY\nReferrer-Policy: strict-origin-when-cross-origin\nPermissions-Policy: camera=(), microphone=(), geolocation=()\n```\n\n## Forms\n\n- CSRF protection on state-changing forms\n- Rate limiting on submission endpoints\n- Validate client and server side\n- Prefer honeypots or light anti-abuse controls over heavy-handed CAPTCHA defaults\n\n### web/testing\n\n> This file extends [common/testing.md](../common/testing.md) with web-specific testing content.\n\n# Web Testing Rules\n\n## Priority Order\n\n### 1. Visual Regression\n\n- Screenshot key breakpoints: 320, 768, 1024, 1440\n- Test hero sections, scrollytelling sections, and meaningful states\n- Use Playwright screenshots for visual-heavy work\n- If both themes exist, test both\n\n### 2. Accessibility\n\n- Run automated accessibility checks\n- Test keyboard navigation\n- Verify reduced-motion behavior\n- Verify color contrast\n\n### 3. Performance\n\n- Run Lighthouse or equivalent against meaningful pages\n- Keep CWV targets from [performance.md](performance.md)\n\n### 4. Cross-Browser\n\n- Minimum: Chrome, Firefox, Safari\n- Test scrolling, motion, and fallback behavior\n\n### 5. Responsive\n\n- Test 320, 375, 768, 1024, 1440, 1920\n- Verify no overflow\n- Verify touch interactions\n\n## E2E Shape\n\n```ts\nimport { test, expect } from '@playwright/test';\n\ntest('landing hero loads', async ({ page }) => {\n await page.goto('/');\n await expect(page.locator('h1')).toBeVisible();\n});\n```\n\n- Avoid flaky timeout-based assertions\n- Prefer deterministic waits\n\n## Unit Tests\n\n- Test utilities, data transforms, and custom hooks\n- For highly visual components, visual regression often carries more signal than brittle markup assertions\n- Visual regression supplements coverage targets; it does not replace them\n"
|
|
14
14
|
},
|
|
15
15
|
"skills": [
|
|
16
16
|
{
|