# 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 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 => { 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 => { 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//` 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/.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/.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 ', '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: ``` (): [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!