# Contributing to Flynn

Thank you for your interest in contributing to Flynn! This guide will help you get started.

## Table of Contents

- [Quick Start](#quick-start)
- [Development Setup](#development-setup)
- [Development Workflow](#development-workflow)
- [Code Style](#code-style)
- [Testing](#testing)
- [Adding Features](#adding-features)
- [Commit Guidelines](#commit-guidelines)
- [Submitting Changes](#submitting-changes)
- [Getting Help](#getting-help)

## Quick Start

```bash
# Clone the repository
git clone <repo-url>
cd flynn

# Install dependencies
pnpm install

# Build the project
pnpm build

# Run the daemon
pnpm start
```

## Development Setup

### Prerequisites

- **Node.js** >= 22.0.0
- **pnpm** (package manager)
- **Docker** (optional, for sandbox features)

### Installation

```bash
# Install dependencies
pnpm install

# Verify TypeScript compiles
pnpm typecheck

# Run linter
pnpm lint

# Run tests
pnpm test
```

### Development Commands

| Command | Description |
|---------|-------------|
| `pnpm build` | Compile TypeScript to `dist/` |
| `pnpm dev` | Run daemon with watch mode (tsx watch) |
| `pnpm start` | Start production build |
| `pnpm tui` | Minimal TUI (readline) |
| `pnpm tui:fs` | Fullscreen TUI (React/Ink) |
| `pnpm test` | Run vitest in watch mode |
| `pnpm test:run` | Run tests once (CI) |
| `pnpm lint` | Run ESLint |
| `pnpm typecheck` | TypeScript check (no emit) |

### Running a Single Test File

```bash
pnpm test:run src/path/to/file.test.ts
```

### Configuration for Development

Create a development config:

```bash
cp config/default.yaml ~/.config/flynn/config.yaml
# Edit config with your API keys and settings
```

## Development Workflow

### Branching Strategy

1. **Main branch**: `main` - stable production code
2. **Feature branches**: `feature/description` - new features
3. **Bugfix branches**: `bugfix/description` - bug fixes
4. **Refactor branches**: `refactor/description` - code improvements

### Feature Development

1. Create a feature branch from `main`
   ```bash
   git checkout -b feature/my-new-feature
   ```

2. Make your changes

3. Build and test
   ```bash
   pnpm build
   pnpm test
   pnpm lint
   pnpm typecheck
   ```

4. Commit your changes (see [Commit Guidelines](#commit-guidelines))

5. Push and create a pull request

### Committing Changes

Before committing, ensure:
- All tests pass: `pnpm test:run`
- Linting passes: `pnpm lint`
- Type checking passes: `pnpm typecheck`
- Build succeeds: `pnpm build`

```bash
git add .
git commit -m "feat: add my new feature"
```

## Code Style

Flynn follows specific conventions documented in [`.planning/codebase/CONVENTIONS.md`](.planning/codebase/CONVENTIONS.md).

### Key Guidelines

- **2-space indentation** (no tabs)
- **Single quotes** for strings
- **Trailing commas** in multiline structures
- **Semicolons** always used
- **camelCase** for functions/variables
- **PascalCase** for classes/interfaces
- **kebab-case** for source files (`my-feature.ts`)
- **PascalCase** for React components (`MyComponent.tsx`)
- Test files co-located with source: `file.test.ts` beside `file.ts`

### Import Organization

```typescript
// 1. Node.js stdlib
import { readFileSync } from 'fs';
import { execFile } from 'child_process';

// 2. Third-party packages
import Anthropic from '@anthropic-ai/sdk';
import { z } from 'zod';

// 3. Local imports (always use .js extension)
import { NativeAgent } from './agent.js';
import type { Config } from '../config/schema.js';
```

### Error Handling

```typescript
// Pattern 1: Return ToolResult with error (tools)
try {
  const result = await someOperation();
  return { success: true, output: result };
} catch (error) {
  return {
    success: false,
    output: '',
    error: error instanceof Error ? error.message : String(error),
  };
}

// Pattern 2: Throw with descriptive message (config/setup)
if (envValue === undefined) {
  throw new Error(`Environment variable ${envVar} is not set`);
}
```

## Testing

### Test Framework

Flynn uses **Vitest** for testing. Test files are co-located with source files:

```
src/
├── agent.ts
├── agent.test.ts
├── models/
│   ├── anthropic.ts
│   └── anthropic.test.ts
```

### Writing Tests

```typescript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { MyComponent } from './my-component.js';

describe('MyComponent', () => {
  beforeEach(() => {
    // Setup before each test
  });

  afterEach(() => {
    // Cleanup after each test
  });

  it('should do something correctly', () => {
    const result = MyComponent.doSomething();
    expect(result).toBe('expected-value');
  });

  it('should handle errors gracefully', () => {
    expect(() => MyComponent.doSomethingInvalid()).toThrow();
  });
});
```

### Testing Guidelines

- Test both success and failure cases
- Clean up resources (files, directories) in `afterEach` or `it` blocks
- Mock external dependencies (APIs, databases, filesystem)
- Use `describe`/`it` pattern for organization
- Keep tests focused and independent

### Running Tests

```bash
# Watch mode (during development)
pnpm test

# Run once (CI/pre-commit)
pnpm test:run

# Run specific test file
pnpm test:run src/models/anthropic.test.ts

# Run tests matching pattern
pnpm test -- --grep "anthropic"
```

## Adding Features

### Adding a New Tool

Flynn tools follow three patterns:

#### Pattern 1: Static Tool (no dependencies)

```typescript
// src/tools/builtin/my-tool.ts
import type { Tool, ToolResult } from '../types.js';

interface MyToolArgs {
  input: string;
}

export const myTool: Tool = {
  name: 'my.tool',
  description: 'Description of what this tool does',
  inputSchema: {
    type: 'object',
    properties: {
      input: { type: 'string', description: 'Input parameter' },
    },
    required: ['input'],
  },
  execute: async (rawArgs: unknown): Promise<ToolResult> => {
    const args = rawArgs as MyToolArgs;
    // Implementation
    return { success: true, output: 'result' };
  },
};
```

#### Pattern 2: Factory Tool (needs dependency injection)

```typescript
// src/tools/builtin/memory-read.ts
import type { Tool, ToolResult } from '../types.js';
import type { MemoryStore } from '../../memory/store.js';

export function createMemoryReadTool(store: MemoryStore): Tool {
  return {
    name: 'memory.read',
    description: 'Read from memory store',
    inputSchema: {
      type: 'object',
      properties: {
        namespace: { type: 'string', description: 'Memory namespace' },
      },
      required: ['namespace'],
    },
    execute: async (rawArgs: unknown): Promise<ToolResult> => {
      const args = rawArgs as { namespace: string };
      try {
        const content = store.read(args.namespace);
        return { success: true, output: content };
      } catch (error) {
        return {
          success: false,
          output: '',
          error: error instanceof Error ? error.message : String(error),
        };
      }
    },
  };
}
```

#### Pattern 3: Multi-Factory (related tool set)

```typescript
// src/tools/builtin/index.ts
export function createMemoryTools(store: MemoryStore, hybridSearch?: HybridSearch): Tool[] {
  return [
    createMemoryReadTool(store),
    createMemoryWriteTool(store),
    createMemorySearchTool(store, hybridSearch),
  ];
}
```

**Registration Steps:**

1. Add export to `src/tools/builtin/index.ts`
2. Add export to `src/tools/index.ts`
3. Register in `src/daemon/index.ts` (call factory + register)
4. Add to tool profiles in `src/tools/policy.ts` if needed
5. Write tests in `src/tools/builtin/my-tool.test.ts`

### Adding a New Channel Adapter

1. Create directory: `src/channels/<platform>/`
2. Create `adapter.ts` implementing `ChannelAdapter` interface
3. Create `index.ts` re-exporting the adapter
4. Add test: `adapter.test.ts`
5. Register in `src/channels/index.ts`
6. Register in `src/daemon/index.ts`
7. Add config schema in `src/config/schema.ts`

### Adding a New Model Provider

1. Create `src/models/<provider>.ts` implementing `ModelClient` interface
2. Add export to `src/models/index.ts`
3. Add case in `src/daemon/index.ts` → `createClientFromConfig()`
4. Add to `src/config/schema.ts` → `modelConfigBaseSchema.provider` enum
5. Write tests in `src/models/<provider>.test.ts`

### Adding a New CLI Command

```typescript
// src/cli/my-cmd.ts
import { Command } from 'commander';
import { loadConfigSafe } from './shared.js';

export function registerMyCommand(program: Command) {
  program
    .command('my-cmd')
    .description('Description of my command')
    .option('-c, --config <path>', 'Config file path')
    .action(async (options) => {
      const configResult = await loadConfigSafe(options.config);
      if (configResult.error) {
        console.error(configResult.error);
        process.exit(1);
      }
      // Implementation
    });
}
```

Register in `src/cli/index.ts`:
```typescript
import { registerMyCommand } from './my-cmd.js';

// In registerCommands()
registerMyCommand(program);
```

## Commit Guidelines

### Commit Message Format

Follow conventional commits:

```
<type>(<scope>): <description>

[optional body]

[optional footer]
```

### Types

- `feat`: New feature
- `fix`: Bug fix
- `refactor`: Code refactoring (no functional change)
- `docs`: Documentation changes
- `test`: Test additions/modifications
- `chore`: Build process, dependencies, tooling
- `style`: Code style changes (formatting, semicolons, etc.)

### Examples

```
feat(tools): add image analysis tool

Implements image analysis using OpenAI Vision API.

Closes #123

fix(gateway): handle WebSocket disconnection gracefully

Prevents infinite loop when connection drops during event emission.

docs(readme): update quick start instructions

Clarify configuration steps for new users.

test(models): add Anthropic client retry tests

Verify exponential backoff and fallback behavior.
```

## Submitting Changes

### Pull Request Process

1. Ensure your branch is up to date with `main`
   ```bash
   git fetch origin
   git rebase origin/main
   ```

2. Push your branch
   ```bash
   git push -u origin feature/my-new-feature
   ```

3. Create a pull request with:
   - Clear description of changes
   - Reference related issues
   - Screenshots if UI changes
   - Test results
   - Breaking changes noted

### Code Review Checklist

Before submitting, verify:

- [ ] All tests pass (`pnpm test:run`)
- [ ] Linting passes (`pnpm lint`)
- [ ] Type checking passes (`pnpm typecheck`)
- [ ] Build succeeds (`pnpm build`)
- [ ] New features have tests
- [ ] Documentation updated (README, AGENTS.md, code comments)
- [ ] No console.log or debugger statements
- [ ] Sensitive data not committed (API keys, tokens)
- [ ] Commit messages follow format

## Getting Help

### Documentation

- **Architecture**: [`.planning/codebase/ARCHITECTURE.md`](.planning/codebase/ARCHITECTURE.md)
- **Structure**: [`.planning/codebase/STRUCTURE.md`](.planning/codebase/STRUCTURE.md)
- **Conventions**: [`.planning/codebase/CONVENTIONS.md`](.planning/codebase/CONVENTIONS.md)
- **Developer Guide**: [`AGENTS.md`](AGENTS.md)
- **User Documentation**: [`README.md`](README.md)

### Troubleshooting

See [`TROUBLESHOOTING.md`](TROUBLESHOOTING.md) for common issues and solutions.

### Questions?

- Open an issue for bugs or feature requests
- Start a discussion for questions
- Check existing issues and discussions first

---

Thank you for contributing to Flynn!
