# Coding Standards

This document defines the coding standards for the Oshun monorepo. These
standards are enforced through ESLint, Prettier, TypeScript compiler options,
and pre-commit hooks.

## Table of Contents

- [TypeScript Configuration](#typescript-configuration)
- [ESLint Rules](#eslint-rules)
- [Prettier Formatting](#prettier-formatting)
- [File Organization](#file-organization)
- [Naming Conventions](#naming-conventions)
- [Import Organization](#import-organization)
- [Error Handling Patterns](#error-handling-patterns)
- [Testing Patterns](#testing-patterns)
- [Domain-Specific Rules](#domain-specific-rules)

---

## TypeScript Configuration

The root `tsconfig.base.json` defines the baseline TypeScript configuration. All
projects extend from it.

### Compiler Options

| Option                             | Value     | Purpose                                |
| ---------------------------------- | --------- | -------------------------------------- |
| `target`                           | `ES2022`  | Modern JavaScript output               |
| `module`                           | `ESNext`  | ESM modules                            |
| `moduleResolution`                 | `bundler` | Bundler-compatible resolution          |
| `strict`                           | `true`    | All strict checks enabled              |
| `noImplicitOverride`               | `true`    | Requires `override` keyword            |
| `noImplicitReturns`                | `true`    | All code paths must return             |
| `noFallthroughCasesInSwitch`       | `true`    | Switch cases must break/return         |
| `noUnusedLocals`                   | `true`    | No unused local variables              |
| `noUnusedParameters`               | `true`    | No unused function parameters          |
| `isolatedModules`                  | `true`    | Required for esbuild/swc compatibility |
| `verbatimModuleSyntax`             | `true`    | Enforces explicit `type` imports       |
| `forceConsistentCasingInFileNames` | `true`    | Path casing must match filesystem      |
| `esModuleInterop`                  | `true`    | CommonJS interop support               |
| `skipLibCheck`                     | `true`    | Skip checking `.d.ts` files            |
| `resolveJsonModule`                | `true`    | Allow importing `.json` files          |
| `experimentalDecorators`           | `true`    | Decorator support                      |
| `emitDecoratorMetadata`            | `true`    | Decorator metadata emission            |
| `sourceMap`                        | `true`    | Source maps for debugging              |
| `useDefineForClassFields`          | `true`    | TC39 class field semantics             |

### Key Implications

**`strict: true`** enables all of these checks:

- `strictNullChecks` -- `null` and `undefined` are distinct types
- `strictFunctionTypes` -- Contravariant function parameter checking
- `strictBindCallApply` -- Strict `bind`, `call`, `apply` typing
- `strictPropertyInitialization` -- Class properties must be initialized
- `noImplicitAny` -- No implicit `any` types
- `noImplicitThis` -- No implicit `this` types
- `alwaysStrict` -- Emit `"use strict"` in output

**`verbatimModuleSyntax: true`** requires explicit type annotations on imports:

```ts
// Correct -- type import is explicit
import { type SomeType, someFunction } from './module.js';

// Correct -- all types
import type { SomeType, AnotherType } from './module.js';

// Incorrect -- will error because SomeType is only used as a type
import { SomeType, someFunction } from './module.js';
```

**`isolatedModules: true`** means each file is compiled independently. This
prohibits:

- `const enum` (use regular `enum` or union types instead)
- Re-exporting types without `type` keyword
- Namespace declarations that merge with values

### Module System

The monorepo uses ESM (`"type": "module"` in root `package.json`). All imports
must use ESM syntax:

```ts
// Correct
import { something } from './module.js';
export { something };

// Incorrect
const something = require('./module');
module.exports = something;
```

### Path Aliases

Path aliases are defined in `tsconfig.base.json`. The convention is:

- **Shared libraries**: `@oshun/{lib}` maps to `libs/shared/{lib}/src/index.ts`
- **Domain libraries**: `@{domain}/{lib}` maps to
  `libs/{domain}/{lib}/src/index.ts`

Examples:

```ts
import { sql } from '@oshun/database';
import { AuthService } from '@oshun/auth';
import type { ConversationConfig } from '@iris/conversation-core';
```

---

## ESLint Rules

The root `eslint.config.js` uses the flat config format with
`typescript-eslint`.

### Global Rules (All TypeScript Files)

#### Type Safety

| Rule                                                | Level   | Notes                                                                                                  |
| --------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------ |
| `@typescript-eslint/no-explicit-any`                | `warn`  | Prefer typed alternatives. Use `unknown` for truly unknown types.                                      |
| `@typescript-eslint/no-unused-vars`                 | `error` | Prefix with `_` for intentionally unused: `_unused`, `_err`. Applies to args, vars, and caught errors. |
| `@typescript-eslint/no-non-null-assertion`          | `warn`  | Avoid `value!`. Use type narrowing or optional chaining instead.                                       |
| `@typescript-eslint/prefer-nullish-coalescing`      | `warn`  | Use `value ?? default` instead of `value \|\| default`.                                                |
| `@typescript-eslint/prefer-optional-chain`          | `warn`  | Use `obj?.prop?.method()` instead of `obj && obj.prop && obj.prop.method()`.                           |
| `@typescript-eslint/explicit-function-return-type`  | `off`   | Return types are inferred. Add them for public API clarity if desired.                                 |
| `@typescript-eslint/explicit-module-boundary-types` | `off`   | Module boundary return types are not required globally.                                                |

#### Best Practices

| Rule                   | Level   | Notes                                                                                                                   |
| ---------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------- |
| `no-console`           | `warn`  | Only `console.warn` and `console.error` are allowed. Use `@oshun/logger` for structured logging.                        |
| `no-debugger`          | `error` | Must be removed before committing.                                                                                      |
| `no-duplicate-imports` | `error` | Combine value and type imports from the same module.                                                                    |
| `eqeqeq`               | `error` | Always use `===` and `!==`. Exception: `null` comparisons (`== null` allowed for checking both `null` and `undefined`). |
| `prefer-const`         | `error` | Use `const` unless the variable is reassigned.                                                                          |
| `no-var`               | `error` | Always use `let` or `const`.                                                                                            |

### Context-Specific Relaxations

**Test files** (`*.spec.ts`, `*.test.ts`, `__tests__/**`):

- `@typescript-eslint/no-explicit-any` -- OFF
- `@typescript-eslint/no-non-null-assertion` -- OFF
- `no-console` -- OFF

**Server/service files** (`**/server.ts`, `**/services/**`):

- `no-console` -- OFF

**CLI applications** (`apps/**/cli/**`):

- `no-console` -- OFF

**Config files** (`*.config.js`, `*.config.ts`):

- `@typescript-eslint/no-require-imports` -- OFF

**Legacy code** (`lilith/**`, `yemaya/**`):

- `@nx/enforce-module-boundaries` -- OFF (disabled during migration)
- `@typescript-eslint/no-explicit-any` -- `warn` (relaxed)
- `@typescript-eslint/no-require-imports` -- OFF

### Module Boundary Rules

The `@nx/enforce-module-boundaries` rule enforces domain isolation. Every
project must have scope, layer, and type tags in its `project.json`.

**Scope tags** define domain ownership:

```
scope:shared, scope:contracts, scope:auth,
scope:lilith, scope:yemaya, scope:isis, scope:iris,
scope:sophia, scope:hathor, scope:bellona, scope:aphrodite,
scope:nyx, scope:tara, scope:veritas, scope:psyche, ...
```

**Layer tags** define architectural layer:

```
layer:contracts -> layer:infra -> layer:data -> layer:domain -> layer:ui
```

**Type tags** define project kind:

```
type:app, type:lib, type:util, type:e2e
```

Constraints:

- `type:app` can depend on `type:lib` and `type:util`
- `type:lib` can depend on `type:lib` and `type:util`
- `type:e2e` can depend on `type:lib`, `type:util`, and `type:app`

---

## Prettier Formatting

Prettier is the sole code formatter. It runs as part of the pre-commit hook via
lint-staged and is checked in CI via `pnpm format:check`.

### Configuration

Prettier uses its default settings with the `prettier-plugin-tailwindcss` plugin
for Tailwind CSS class sorting. No custom `.prettierrc` file exists at the root
-- Prettier defaults apply.

**Key defaults:**

| Setting         | Value       |
| --------------- | ----------- |
| Print width     | 80          |
| Tab width       | 2           |
| Tabs            | No (spaces) |
| Semicolons      | Yes         |
| Quotes          | Double      |
| Trailing comma  | `all`       |
| Bracket spacing | Yes         |
| Arrow parens    | `always`    |
| End of line     | `lf`        |

### File Types Formatted

Prettier formats these file types (configured in lint-staged):

- `.js`, `.jsx`, `.ts`, `.tsx` -- Also linted with ESLint first
- `.json`
- `.md`
- `.yaml`, `.yml`

### Running Prettier

```bash
# Format all files
pnpm format

# Check formatting (CI mode)
pnpm format:check
```

---

## File Organization

### Library Structure

Every library follows this standard structure:

```
libs/{domain}/{library-name}/
  src/
    index.ts          # Public API barrel file
    types.ts          # Type definitions
    {module}.ts       # Implementation files
    __tests__/        # Test directory (alternative to co-located tests)
      {module}.spec.ts
  project.json        # Nx project configuration
  package.json        # Package metadata and dependencies
  tsconfig.json       # Extends ../../tsconfig.base.json (or ../../../)
  tsconfig.lib.json   # Library-specific TS config
  tsconfig.spec.json  # Test-specific TS config
  vitest.config.ts    # Vitest configuration
```

### Public API (index.ts)

The `index.ts` barrel file defines the public API of the library. It should
export all public types, functions, and classes:

```ts
/**
 * @{scope}/{library-name}
 *
 * Description of what this library provides.
 *
 * @packageDocumentation
 */

// Types
export type { TypeA, TypeB, TypeC } from './types.js';

// Constants and defaults
export { DEFAULT_CONFIG, SOME_CONSTANT } from './types.js';

// Implementation
export { ServiceClass, createService } from './service.js';
export { helperFunction } from './helpers.js';
```

Conventions:

- Group exports by category (types, constants, implementations)
- Use section comments (`// ====...`) to visually separate categories
- Include JSDoc module documentation at the top
- Export types separately using `export type` (required by
  `verbatimModuleSyntax`)

### Application Structure

Applications follow a domain-appropriate structure. Common patterns:

```
apps/{domain}/{app-name}/
  src/
    app.ts            # Application entry point
    routes/           # API route handlers
    services/         # Business logic services
    middleware/       # Request middleware
  test/               # Test files
  project.json
  package.json
  tsconfig.json
```

### Project Configuration (project.json)

Every project must include scope, layer, and type tags:

```json
{
  "name": "@domain/library-name",
  "sourceRoot": "libs/domain/library-name/src",
  "projectType": "library",
  "tags": ["scope:domain", "layer:domain", "type:lib"],
  "targets": {
    "build": {
      "executor": "@nx/js:tsc",
      "options": {
        "outputPath": "dist/libs/domain/library-name",
        "main": "libs/domain/library-name/src/index.ts",
        "tsConfig": "libs/domain/library-name/tsconfig.lib.json"
      }
    },
    "test": {
      "executor": "@nx/vite:test",
      "options": {
        "config": "libs/domain/library-name/vitest.config.ts"
      }
    },
    "lint": {
      "executor": "@nx/eslint:lint"
    }
  }
}
```

---

## Naming Conventions

### Files and Directories

| Type                  | Convention       | Example                |
| --------------------- | ---------------- | ---------------------- |
| Source files          | `kebab-case.ts`  | `auth-service.ts`      |
| Test files            | `*.spec.ts`      | `auth-service.spec.ts` |
| Test files (alt)      | `*.test.ts`      | `auth-service.test.ts` |
| Type definition files | `types.ts`       | `types.ts`             |
| Index/barrel files    | `index.ts`       | `index.ts`             |
| Config files          | `*.config.ts`    | `vitest.config.ts`     |
| Directories           | `kebab-case`     | `auth-primitives/`     |
| React components      | `PascalCase.tsx` | `MeditationCard.tsx`   |
| CSS modules           | `*.module.css`   | `SafeArea.module.css`  |

### Code Identifiers

| Type           | Convention                  | Example                            |
| -------------- | --------------------------- | ---------------------------------- |
| Variables      | `camelCase`                 | `const userName = 'john';`         |
| Constants      | `UPPER_CASE` or `camelCase` | `const MAX_RETRIES = 3;`           |
| Functions      | `camelCase`                 | `function createService() {}`      |
| Classes        | `PascalCase`                | `class AuthService {}`             |
| Interfaces     | `PascalCase`                | `interface UserProfile {}`         |
| Type aliases   | `PascalCase`                | `type TokenPair = { ... };`        |
| Enums          | `PascalCase`                | `enum UserRole { Admin, User }`    |
| Enum members   | `PascalCase`                | `UserRole.Admin`                   |
| Generics       | `PascalCase`                | `function wrap<T>(value: T): T {}` |
| Private fields | `_camelCase`                | `private _connectionPool: Pool;`   |
| Unused params  | `_camelCase`                | `(_req, res) => {}`                |

### Package Naming

| Type             | Pattern                | Example                   |
| ---------------- | ---------------------- | ------------------------- |
| Shared libraries | `@oshun/{lib-name}`    | `@oshun/database`         |
| Domain libraries | `@{domain}/{lib-name}` | `@iris/conversation-core` |
| Applications     | `@{domain}/{app-name}` | `@tara/api`               |

### Interface Naming

Prefer descriptive names without `I` prefix for most interfaces. Use `I` prefix
for repository/store interfaces that represent contracts:

```ts
// Repository contracts -- use I prefix
interface IUserRepository { ... }
interface ITokenRepository { ... }
interface IAuditRepository { ... }

// Data shape interfaces -- no I prefix
interface UserProfile { ... }
interface AuthResult { ... }
interface TokenPair { ... }
```

---

## Import Organization

### Import Order

Organize imports in this order, separated by blank lines:

1. **Node.js built-in modules** (`node:fs`, `node:path`, etc.)
2. **External packages** (`zod`, `hono`, `vitest`, etc.)
3. **Workspace packages** (`@oshun/*`, `@iris/*`, etc.)
4. **Relative imports** (`./module`, `../module`)

```ts
import { readFile } from 'node:fs/promises';

import { z } from 'zod';
import { Hono } from 'hono';

import { sql, type ParameterizedQuery } from '@oshun/database';
import { AuthService } from '@oshun/auth';

import { processData } from './helpers.js';
import type { Config } from './types.js';
```

### Import Rules

1. **No duplicate imports** (`no-duplicate-imports: error`). Combine value and
   type imports from the same module:

   ```ts
   // Correct
   import { sql, type ParameterizedQuery } from '@oshun/database';

   // Incorrect -- separate imports from same module
   import { sql } from '@oshun/database';
   import type { ParameterizedQuery } from '@oshun/database';
   ```

2. **Use `type` keyword for type-only imports** (required by
   `verbatimModuleSyntax`):

   ```ts
   // Type-only import
   import type { UserProfile } from './types.js';

   // Mixed import with inline type
   import { AuthService, type AuthConfig } from '@oshun/auth';
   ```

3. **Use `.js` extension for relative imports** (required for ESM):

   ```ts
   // Correct
   import { helper } from './helpers.js';

   // Incorrect
   import { helper } from './helpers';
   ```

---

## Error Handling Patterns

### General Principles

1. **Never swallow errors silently.** Always log or re-throw.

2. **Use typed errors.** The `@oshun/errors` library provides structured error
   types. Domain libraries define their own typed error classes:

   ```ts
   export class AuthError extends Error {
     constructor(
       public code: AuthErrorCode,
       message: string,
       public details?: Record<string, unknown>
     ) {
       super(message);
       this.name = 'AuthError';
     }
   }
   ```

3. **Use `unknown` for catch blocks** (TypeScript strict mode requires this):

   ```ts
   try {
     await riskyOperation();
   } catch (error: unknown) {
     if (error instanceof AuthError) {
       // Handle typed error
     } else {
       // Handle unknown error
       throw new Error('Unexpected error', { cause: error });
     }
   }
   ```

4. **Prefix unused caught errors with `_`:**

   ```ts
   try {
     await operation();
   } catch (_error) {
     // Intentionally ignoring the error
     return fallbackValue;
   }
   ```

### Result Pattern

For functions that can fail in expected ways, consider returning a result type
instead of throwing:

```ts
type Result<T, E = Error> =
  | { success: true; data: T }
  | { success: false; error: E };

async function authenticate(
  credentials: LoginCredentials
): Promise<Result<AuthResult, AuthError>> {
  // ...
}
```

### Never Expose Internal Errors

API error responses must never include stack traces, internal paths, or
implementation details in production:

```ts
// Correct -- structured error response
return { error: { code: 'UNAUTHORIZED', message: 'Invalid credentials' } };

// Incorrect -- leaks internals
return { error: err.stack };
```

---

## Testing Patterns

### Framework

All tests use **Vitest** as the test runner, configured per-project via
`vitest.config.ts`.

### Vitest Configuration

Each project's `vitest.config.ts` must include path aliases that match
`tsconfig.base.json`:

```ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    environment: 'node',
  },
  resolve: {
    alias: {
      '@oshun/database': '../database/src/index.ts',
      '@oshun/errors': '../errors/src/index.ts',
    },
  },
});
```

### Test File Naming

- **Unit tests**: `*.spec.ts` or `*.test.ts` -- co-located with source or in
  `__tests__/` directory
- **Integration tests**: `*.integration.test.ts` or in `test/` directory
- **E2E tests**: In a dedicated `e2e/` directory with Playwright

### Test Structure

Use descriptive `describe` and `it` blocks:

```ts
import { describe, it, expect, beforeEach, vi } from 'vitest';

describe('AuthService', () => {
  let service: AuthService;

  beforeEach(() => {
    service = createAuthService(mockConfig);
  });

  describe('login', () => {
    it('returns token pair for valid credentials', async () => {
      const result = await service.login({
        email: 'user@example.com',
        password: 'ValidPass123!',
      });

      expect(result.success).toBe(true);
      expect(result.data.accessToken).toBeDefined();
      expect(result.data.refreshToken).toBeDefined();
    });

    it('returns error for invalid password', async () => {
      const result = await service.login({
        email: 'user@example.com',
        password: 'wrong',
      });

      expect(result.success).toBe(false);
      expect(result.error.code).toBe('INVALID_CREDENTIALS');
    });
  });
});
```

### Assertion Patterns

Common Vitest assertion patterns and their correct usage:

```ts
// Equality
expect(value).toBe(expected); // Strict equality (===)
expect(value).toEqual(expected); // Deep equality
expect(value).toBeCloseTo(3.14, 2); // Floating point (within 0.005)

// Array/object matching
expect(array).toContain(item); // Uses === (for primitives)
expect(array).toContainEqual(item); // Uses deep equality (for objects)
expect(obj).toMatchObject(partial); // Partial object match

// Truthiness
expect(value).toBeDefined();
expect(value).toBeUndefined();
expect(value).toBeTruthy();
expect(value).toBeFalsy();

// Async
await expect(promise).resolves.toBe(value);
await expect(promise).rejects.toThrow('message');
```

### Mocking

Use Vitest's built-in mocking:

```ts
import { vi } from 'vitest';

// Mock a module
vi.mock('@oshun/database', () => ({
  sql: vi.fn(),
  createPool: vi.fn(),
}));

// Mock a function
const mockFn = vi.fn().mockReturnValue('result');

// Spy on a method
const spy = vi.spyOn(service, 'method');

// Reset mocks between tests
afterEach(() => {
  vi.restoreAllMocks();
});
```

---

## Domain-Specific Rules

### Uzume Domain (Live Performance & Stagecraft)

The Uzume domain (`libs/uzume/**`) enforces stricter TypeScript rules due to its
real-time nature and entertainment protocol integrations:

| Rule                                                | Level   | Notes                                         |
| --------------------------------------------------- | ------- | --------------------------------------------- |
| `@typescript-eslint/no-explicit-any`                | `error` | No `any` allowed                              |
| `@typescript-eslint/explicit-function-return-type`  | `error` | All functions must have return types          |
| `@typescript-eslint/explicit-module-boundary-types` | `error` | All exported functions must have return types |

Naming conventions are strictly enforced:

- Default identifiers: `camelCase`
- Variables: `camelCase`, `UPPER_CASE`, or `PascalCase`
- Types/interfaces: `PascalCase`
- Enum members: `UPPER_CASE` or `PascalCase`
- Object literal and type properties: unrestricted (for protocol compatibility)

Entertainment protocol abbreviations are allowed in identifiers: `DMX`, `OSC`,
`MIDI`, `SMPTE`, `GDTF`, `MVR`, `NDI`, `AES`, `LTC`, `MTC`, `RDM`, `ArtNet`,
`sACN`

### Browser Extension (Iris)

Browser extension files (`apps/iris/web/extension/**/*.js`) have explicit
browser and WebExtension globals defined and relaxed rules for switch-case
patterns.

### PWA and Widget (Iris)

PWA files (`apps/iris/web/pwa/**`) and widget files (`apps/iris/web/widget/**`)
have `no-console` disabled and relaxed nullish coalescing rules for DOM
manipulation patterns.

### Code Generation Libraries (Iris)

Code generation and agentic libraries (`libs/iris/code/generation/**`,
`libs/iris/code/agentic/**`) have `no-case-declarations` disabled due to
extensive switch-case patterns.

---

## Pre-commit Enforcement Summary

Every commit is automatically checked by these hooks (via Husky):

1. **Pre-commit** (`lint-staged`):
   - `eslint --fix` on staged `.js`, `.jsx`, `.ts`, `.tsx` files
   - `prettier --write` on staged `.js`, `.jsx`, `.ts`, `.tsx`, `.json`, `.md`,
     `.yaml`, `.yml` files

2. **Commit-msg** (`commitlint`):
   - Validates the commit message against Conventional Commits format
   - Enforces type, scope, subject, and length rules

If either hook fails, the commit is rejected. Fix the issues and try again.
