# Contributing to Lexicon Client

Thank you for your interest in contributing to the Lexicon Client! This project follows a **README-Driven Development (RDD)** approach with **Test-Driven Development (TDD)** principles.

## 🚀 Quick Start

```bash
# 1. Fork and clone the repository
git clone https://github.com/juanpprieto/lexicon-client.git
cd lexicon-client

# 2. Install dependencies
npm install

# 3. Run tests to see current status
npm run test:help  # See all available test commands
npm run test:all   # Run all tests (will fail - that's expected!)
```

## 📋 Development Principles

### 1. README-Driven Development (RDD)

- **README.md is the single source of truth** for all API specifications
- No features or changes without README documentation first
- Implementation must match README examples exactly

### 2. Test-Driven Development (TDD)

- **All tests currently FAIL** - this is intentional (Phase 2 complete)
- Write failing tests first, then implement to make them pass
- Tests define the exact client behavior expected

### 3. Type Safety First

- **Zero runtime surprises** - everything validated at compile-time
- Zod schemas for runtime validation + TypeScript inference
- OpenAPI auto-generated types for API compliance

## 🏗️ Project Architecture

### Type System (Atomic Design)

```
src/schemas/index.ts     # Single source of truth - Zod schemas
src/types/index.ts       # Re-exports inferred types
src/types/generated.ts   # Auto-generated from OpenAPI
src/types/client/        # Domain-organized client interfaces
  ├── base.ts           # Base operation interfaces
  ├── tracks.ts         # client.tracks.* namespace
  ├── playlists.ts      # client.playlists.* namespace
  ├── tags.ts           # client.tags.* namespace
  └── main.ts           # Combined LexiconClient interface
```

### Test Structure (MSW + Vitest)

```
src/__tests__/
├── units/              # Domain-specific API tests
│   ├── tracks.test.ts     # 70+ test scenarios
│   ├── playlists.test.ts  # 45+ test scenarios
│   ├── tags.test.ts       # 85+ test scenarios
│   ├── player.test.ts     # 25+ test scenarios
│   └── client.test.ts     # 15+ lifecycle tests
├── integration/        # End-to-end workflows
├── mocks/             # MSW test infrastructure
│   ├── handlers/      # Type-safe API handlers
│   └── fixtures/      # Dynamic test data factories
└── utils/             # Result<T,E> test helpers
```

## 🧪 Testing Workflow

### Available Test Commands

```bash
# Unit Tests by Domain
npm run test:tracks      # Test tracks API (70+ scenarios)
npm run test:playlists   # Test playlists API (45+ scenarios)
npm run test:tags        # Test tags & categories (85+ scenarios)
npm run test:player      # Test player & control (25+ scenarios)
npm run test:client      # Test client lifecycle (15+ scenarios)

# Integration & Workflows
npm run test:integration # End-to-end workflows
npm run test:all        # All unit tests

# Coverage Reports
npm run test:coverage:tracks    # Domain-specific coverage
npm run test:coverage          # Full coverage report

# Development
npm run test:watch:tracks      # Watch mode for specific domain
npm run test:help             # Show all commands
```

### TDD Cycle

1. **Red** - Tests fail (current state)
2. **Green** - Implement minimum code to pass tests
3. **Refactor** - Improve code while keeping tests green

## 🛠️ Development Setup

### Prerequisites

- Node.js 18+ or Bun 1.0+
- TypeScript knowledge
- Understanding of Result<T,E> pattern (neverthrow)

### Code Quality Tools

```bash
npm run lint           # ESLint check
npm run lint:fix       # Auto-fix linting issues
npm run format         # Prettier formatting
npm run typecheck      # TypeScript compilation check
npm run generate:types # Update OpenAPI types
```

### Git Workflow

- Branch naming: `feature/domain-description` (e.g., `feature/tracks-iteration-memory-safety`)
- Commit frequently with clear messages
- Use conventional commits: `feat:`, `fix:`, `test:`, `docs:`

## 📝 Contributing Guidelines

### 1. Adding New Features

**IMPORTANT**: All changes must start with README.md updates

```bash
# 1. Update README.md API specification first
# 2. Add corresponding test scenarios
npm run test:tracks  # Should fail with new scenarios

# 3. Implement feature to make tests pass
# 4. Verify all tests pass
npm run test:all
npm run test:coverage

# 5. Update types if needed
npm run generate:types
npm run typecheck
```

### 2. Bug Fixes

```bash
# 1. Add failing test that reproduces the bug
# 2. Fix implementation to make test pass
# 3. Ensure no regressions
npm run test:all
```

### 3. Test Development

#### Adding New Test Scenarios

- Place tests in appropriate domain file (`tracks.test.ts`, etc.)
- Use MSW for realistic API mocking
- Follow existing test patterns:

```typescript
describe('client.tracks.newFeature()', () => {
  const client = createClient()

  it('should handle the new feature correctly', async () => {
    const result = await client.tracks.newFeature({ param: 'value' })

    const response = expectOk(result)
    expect(response.data).toBeDefined()
  })

  it('should handle error scenarios', async () => {
    const result = await client.tracks.newFeature({ invalid: 'data' })

    expectValidationError(result)
  })
})
```

#### MSW Handler Development

- Add handlers in `src/__tests__/mocks/handlers/`
- Use type-safe request/response handling
- Include realistic error scenarios

### 4. Type System Changes

#### Schema Updates (Primary)

```typescript
// src/schemas/index.ts - Single source of truth
export const NewFeatureSchema = z.object({
  id: z.number(),
  name: z.string().min(1).max(255),
  // ... other fields
})

export type NewFeature = z.infer<typeof NewFeatureSchema>
```

#### Client Interface Updates

```typescript
// src/types/client/domain.ts - Domain-specific interfaces
export interface DomainNamespace {
  newFeature(options: NewFeatureOptions): Promise<Result<NewFeature, ApiError>>
}
```

## 🔍 Code Review Process

### Before Submitting PR

**Checklist:**

- [ ] README.md updated with new API specifications
- [ ] Tests added and currently failing (TDD)
- [ ] Implementation makes tests pass
- [ ] All existing tests still pass: `npm run test:all`
- [ ] Type safety verified: `npm run typecheck`
- [ ] Code formatted: `npm run format`
- [ ] No linting errors: `npm run lint`
- [ ] Coverage maintained: `npm run test:coverage`

### PR Requirements

1. **Title**: Clear, descriptive (e.g., "Add memory-safe track iteration with cancellation")
2. **Description**:
   - What problem does this solve?
   - How does it solve it?
   - Link to any related issues
3. **Tests**: Include test output showing before/after
4. **Breaking Changes**: Clearly marked if any

## 🏷️ Issue Labels

- `bug` - Something isn't working
- `enhancement` - New feature or request
- `documentation` - Improvements to docs
- `good first issue` - Good for newcomers
- `help wanted` - Extra attention needed
- `test` - Test-related changes
- `type-system` - Schema or type changes

## 📚 Key Concepts

### Result<T, E> Pattern

```typescript
// Never throw exceptions - always return Results
const result = await client.tracks.get({ id: 1 })

if (result.isOk()) {
  const track = result.value // Type: Track
  console.log(track.title)
} else {
  const error = result.error // Type: ApiError
  console.error(error.message)
}
```

### Zod Schema Design

```typescript
// Atomic, composable schemas
export const BaseTrackSchema = z.object({
  id: z.number().optional(),
  title: z.string().optional(),
})

export const TrackCreateSchema = BaseTrackSchema.pick({
  title: true,
  // ... required fields for creation
})

export type Track = z.infer<typeof BaseTrackSchema>
export type TrackCreate = z.infer<typeof TrackCreateSchema>
```

### MSW Testing Pattern

```typescript
// Type-safe, realistic API mocking
export const trackGetHandler = http.get<never, never, Track | ApiError>(
  `${BASE_URL}/track`,
  ({ request }) => {
    const url = new URL(request.url)
    const id = Number(url.searchParams.get('id'))

    if (id === 999999) {
      return HttpResponse.json(createTrackNotFoundError(id), { status: 404 })
    }

    return HttpResponse.json(createTrackFixture({ id }))
  }
)
```

## 🎯 Current Status

**Phase 2 (TDD) Complete ✅**

- 275+ failing test scenarios across all domains
- MSW test infrastructure with type-safe handlers
- Comprehensive error scenario coverage
- Memory safety and performance tests

**Phase 3 (Implementation) Ready 🚀**

- All tests define exact client behavior
- Type system provides implementation guidance
- README.md specifies all API requirements

## 🤝 Getting Help

- **Questions?** Open a Discussion
- **Bug reports?** Create an Issue with reproduction steps
- **Feature requests?** Start with README.md proposal
- **Need guidance?** Check existing test files for patterns

## 📦 Release Process

This project uses [Changesets](https://github.com/changesets/changesets) for version management and automated releases.

### Adding a Changeset

**Required for every PR that modifies functionality.** CI will enforce this.

```bash
# Create a changeset describing your changes
npm run changeset

# Follow the prompts:
# 1. Select change type: patch | minor | major
# 2. Write a summary of the change
```

**Changeset guidelines:**

- **patch**: Bug fixes, dependency updates, documentation
- **minor**: New features, backwards-compatible changes
- **major**: Breaking changes (avoid if possible)

**Example changeset:**

```markdown
---
'@juanpprieto/lexicon-client': patch
---

Add support for archived field in track updates
```

### Empty Changesets

If your PR doesn't require a release (e.g., README updates, test refactoring):

```bash
npm run changeset -- --empty
```

### Release Workflow

Releases use a **Release PR pattern** for controlled, batched releases:

1. **Merge feature PR with changeset** → Changeset accumulates in main
2. **Release PR auto-created/updated** → GitHub Actions creates a PR named "chore: release package" on branch `changeset-release/main`
3. **Review release PR** → Contains version bumps and changelog updates for all accumulated changesets
4. **Merge release PR (owner only)** → Publishes to npm + creates GitHub release

**Key points:**

- Multiple feature PRs can be merged before releasing (changesets accumulate)
- Only repository owner (@juanpprieto) can approve and merge the release PR
- Merging the release PR triggers the actual npm publish
- Branch protection ensures all PRs require CI to pass before merge

### Security Gates

Releases are blocked if:

- Critical vulnerabilities detected (`npm audit --audit-level=critical`)
- Any CI check fails (quality, tests, build, security)
- Release PR cannot be merged without passing CI

### Branch Protection (Repository Owner)

For repository admins, enable these protections on `main`:

**Required settings:**

- ✅ Require pull request before merging
- ✅ Require status checks to pass (Code Quality, Changeset Check, Security Scan)
- ✅ Restrict who can approve pull requests (only @juanpprieto for release PRs)
- ✅ Do not allow bypassing the above settings

```bash
# Via GitHub CLI
gh api repos/juanpprieto/lexicon-client/branches/main/protection \
  --method PUT \
  --field "required_status_checks[strict]=true" \
  --field "required_status_checks[contexts][]=Code Quality" \
  --field "required_status_checks[contexts][]=Changeset Check" \
  --field "required_status_checks[contexts][]=Security Scan" \
  --field "required_status_checks[contexts][]=Security Check" \
  --field "enforce_admins=false" \
  --field "required_pull_request_reviews[required_approving_review_count]=1" \
  --field "required_pull_request_reviews[dismiss_stale_reviews]=true" \
  --field "required_pull_request_reviews[require_code_owner_reviews]=false" \
  --field "restrictions=null"
```

Or via GitHub UI: Settings → Branches → Add branch protection rule

**For release control:** Consider using a CODEOWNERS file to require owner approval for release PRs:

```
# .github/CODEOWNERS
# Release PRs require owner approval
.changeset/ @juanpprieto
```

### Manual Release (Emergency)

```bash
# 1. Ensure you're on main with latest changes
git checkout main && git pull

# 2. Run validation
npm run validate

# 3. Build package
npm run build

# 4. Publish (requires NPM_TOKEN or OIDC auth)
npm run changeset:publish
```

### Checking Release Status

```bash
# See pending releases
npm run changeset:status

# View current version
npm version
```

## 📄 License

By contributing, you agree that your contributions will be licensed under the MIT License.

---

**Happy Contributing! 🎵** Let's build the best DJ library client together.
