Thank you for your interest in contributing to Ralph TUI! This document provides guidelines and information for contributors.
- Bun >= 1.0.0 (runtime and package manager)
- Git for version control
# Clone the repository
git clone https://github.com/subsy/ralph-tui.git
cd ralph-tui
# Install dependencies
bun install
# Run the TUI in development mode
bun run ./src/cli.tsx
# Or use the dev script
bun run dev# Type checking
bun run typecheck
# Linting
bun run lint
# Fix lint issues automatically
bun run lint:fix
# Build the project
bun run build- Simplicity over cleverness - Write clear, maintainable code
- Minimal changes - Make the smallest reasonable change to achieve the goal
- No unrelated changes - Don't refactor or "improve" code outside your scope
- Preserve comments - Only remove comments if they're provably false
- Strict mode is enabled - all code must be fully typed
- Use
ReactNodereturn type for component functions (notJSX.Element) - Use ES modules (
import/export) - the package is"type": "module"
Every source file must start with an ABOUTME comment explaining the file's purpose:
/**
* ABOUTME: Brief description of what this file does.
* Additional context if needed.
*/src/
├── cli.tsx # CLI entry point
├── commands/ # CLI command implementations
├── config/ # Configuration system
├── engine/ # Execution engine
├── interruption/ # Signal handling
├── logs/ # Iteration logging
├── plugins/
│ ├── agents/ # Agent plugins (claude, opencode, gemini, codex, kiro)
│ └── trackers/ # Tracker plugins (beads, json)
├── session/ # Session management
├── setup/ # Interactive setup wizard
├── templates/ # Prompt templates
└── tui/
└── components/ # OpenTUI React components
- Tracker plugins go in
src/plugins/trackers/builtin/ - Agent plugins go in
src/plugins/agents/builtin/ - Use the factory pattern with singleton registries
- Extend
BaseTrackerPluginorBaseAgentPluginfor common functionality
- Components go in
src/tui/components/ - Use OpenTUI's React bindings (
@opentui/react) - Export components from
src/tui/components/index.ts
- Create a branch from
main - Make your changes following the code style guidelines
- Run quality checks:
bun run typecheck && bun run lint - Test your changes manually with
bun run ./src/cli.tsx - Commit with a descriptive message
- Open a pull request
Use conventional commit format:
type: brief description
Longer explanation if needed.
Types:
feat:- New featurefix:- Bug fixdocs:- Documentation changesrefactor:- Code refactoringchore:- Maintenance tasks
Examples:
feat: add support for custom prompt templates
fix: handle empty task lists gracefully
docs: update README with configuration examples
- One feature per PR - Keep changes focused
- Update documentation if adding features - any new or changed functionality must include documentation updates
- Ensure CI passes - typecheck, lint, and tests must succeed
- Meet test coverage requirements - PRs must have >50% test coverage on new and changed lines (enforced by Codecov)
- Describe your changes in the PR description
All contributions must meet these requirements before merging:
- Test coverage >50% - New/changed code must have at least 50% unit test coverage (enforced by Codecov)
- Typecheck passes - Run
bun run typecheckwith no errors - Lint passes - Run
bun run lintwith no errors - Build succeeds - Run
bun run buildwith no errors
- Docs for user-facing changes - Any new or changed user-facing behavior must include documentation updates
- ABOUTME header - All new source files must have an ABOUTME comment header
Complete this checklist when adding a new tracker plugin:
- Create plugin directory:
src/plugins/trackers/builtin/<tracker-name>/ - Create main plugin file:
src/plugins/trackers/builtin/<tracker-name>/index.ts - Include ABOUTME header comment in all source files
- Extend
BaseTrackerPluginor implementTrackerPlugininterface - Implement required interface methods:
-
meta- Plugin metadata (id, name, description, version, author) -
initialize(config)- Configuration initialization -
detect()- Check if tracker CLI/tool is available -
getTasks()- Fetch tasks from the tracker -
getTask(id)- Fetch a single task by ID -
updateTask(id, updates)- Update task status/fields -
getSetupQuestions()- Setup wizard questions -
validateSetup(answers)- Validate setup answers
-
- Export factory function:
const createMyTracker: TrackerPluginFactory = () => new MyTrackerPlugin();
- Import factory in
src/plugins/trackers/builtin/index.ts - Add to
builtinTrackersobject - Add to
registerBuiltinTrackers()function - Export the factory function
- Create test file:
src/plugins/trackers/builtin/<tracker-name>.test.tsortests/plugins/trackers/<tracker-name>.test.ts - Test plugin metadata
- Test
initialize()with various configs - Test
detect()success and failure cases - Test
getTasks()parsing - Test
getTask()by ID - Test
updateTask()operations - Test
getSetupQuestions()returns valid questions - Test
validateSetup()with valid/invalid inputs - Test error handling for CLI failures
- Achieve >50% coverage on new code
- Create docs page:
website/content/docs/plugins/trackers/<tracker-name>.mdx - Include sections: Overview, Prerequisites, Basic Usage, Configuration, Options Reference, How It Works, Troubleshooting
- Add to navigation:
website/lib/navigation.ts(in Trackers section)
Complete this checklist when adding a new agent plugin:
- Create plugin file:
src/plugins/agents/builtin/<agent-name>.ts - Include ABOUTME header comment
- Extend
BaseAgentPluginor implementAgentPlugininterface - Define
metawith required fields:-
id- Unique plugin identifier (e.g.,'cursor') -
name- Display name (e.g.,'Cursor Agent') -
description- Brief description -
version- Plugin version -
author- Author/organization name -
defaultCommand- CLI command name (e.g.,'cursor') -
supportsStreaming- Whether agent supports streaming output -
supportsInterrupt- Whether agent can be interrupted -
supportsFileContext- Whether agent accepts file context -
supportsSubagentTracing- Whether agent emits structured JSONL -
structuredOutputFormat- Output format (e.g.,'jsonl') -
skillsPaths- Personal and repo skill paths (optional)
-
- Implement required methods:
-
initialize(config)- Configuration initialization -
detect()- Check if agent CLI is available and get version -
execute(prompt, files?, options?)- Execute agent with prompt -
getSandboxRequirements()- Auth/binary paths needed -
getSetupQuestions()- Setup wizard questions -
validateSetup(answers)- Validate setup answers -
validateModel(model)- Validate model name (if applicable)
-
- Implement protected methods:
-
buildArgs(prompt, files?, options?)- Build CLI arguments -
getStdinInput(prompt, files?, options?)- Provide stdin input (if needed) -
getPreflightSuggestion()- Troubleshooting suggestions
-
- If agent outputs JSONL, implement parsing functions:
-
parse<Agent>JsonLine(jsonLine)- Parse single JSONL line toAgentDisplayEvent[] -
parse<Agent>OutputToEvents(data)- Parse full output to events
-
- Use shared utilities from
src/plugins/agents/utils.ts(e.g.,extractErrorMessage) - Export factory function:
const create<Agent>Agent: AgentPluginFactory = () => new <Agent>AgentPlugin();
- Import factory in
src/plugins/agents/builtin/index.ts - Add to
registerBuiltinAgents()function - Export the factory function
- Export the plugin class
- Add to
AGENT_ID_MAPinsrc/setup/skill-installer.ts
- Create test file:
src/plugins/agents/builtin/<agent-name>.test.ts - Test plugin metadata (id, name, defaultCommand, supports* flags)
- Test
initialize()with various configs (model, timeout, agent-specific options) - Test
getSetupQuestions()returns all expected questions - Test
validateSetup()with valid/invalid inputs - Test
validateModel()with various model names - Test
getSandboxRequirements()returns expected paths - Test
buildArgs()produces correct CLI arguments - Test
getStdinInput()returns prompt correctly - If JSONL parsing:
- Test parsing valid JSONL events (text, tool_use, tool_result, error)
- Test handling of empty/invalid input
- Test handling of mixed valid/invalid lines
- Test edge cases (missing fields, malformed JSON)
- Achieve >50% coverage on new code
- Create docs page:
website/content/docs/plugins/agents/<agent-name>.mdx - Include sections:
- Overview with feature highlights
- Prerequisites (installation instructions)
- Basic Usage (with
<Steps>component) - Configuration (shorthand and full config examples)
- Options Reference (table with all options)
- Agent-specific features (modes, sandbox, etc.)
- Subagent Tracing (if supported)
- How It Works (CLI arguments built)
- Troubleshooting (common issues and fixes)
- Next Steps (links to related docs)
- Add to navigation:
website/lib/navigation.ts(in Agents section) - Use
label: 'New'in navigation for new agents
- Create a command file in
src/commands/ - Export from
src/commands/index.ts - Add handling in
src/cli.tsx - Update the help text in
showHelp() - Create tests in
tests/commands/orsrc/commands/*.test.ts - Update documentation if user-facing
Ralph TUI uses Bun's built-in test runner for unit and integration tests.
# Run all tests
bun test
# Run tests in watch mode (re-runs on file changes)
bun test --watch
# Run tests with coverage report
bun test --coverage
# Run specific test file
bun test tests/plugins/claude-agent.test.ts
# Run tests matching a pattern
bun test --grep "ExecutionEngine"- Test files are placed in the
tests/directory - Test files must end with
.test.ts - Mirror the
src/structure:src/plugins/agents/→tests/plugins/ - Name test files after the module they test:
claude.ts→claude-agent.test.ts
tests/
├── commands/ # CLI command tests
├── engine/ # Execution engine tests
├── factories/ # Reusable test data factories
│ ├── agent-config.ts
│ ├── prd-data.ts
│ ├── session-state.ts
│ ├── tracker-config.ts
│ └── tracker-task.ts
├── fixtures/ # Static test data (JSON, configs)
├── mocks/ # Mock implementations
│ ├── agent-responses.ts
│ ├── child-process.ts
│ └── file-system.ts
├── plugins/ # Plugin tests (agents, trackers)
├── tui/ # TUI component tests
├── utils/ # Utility function tests
└── index.ts # Test utilities exports
Every test file must start with an ABOUTME comment:
/**
* ABOUTME: Tests for the ExecutionEngine.
* Tests state machine transitions, iteration logic, and error handling.
*/
import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test';describe('ModuleName', () => {
let instance: MyClass;
beforeEach(() => {
instance = new MyClass();
});
afterEach(async () => {
await instance.dispose();
});
describe('methodName', () => {
test('should do expected behavior', () => {
const result = instance.methodName();
expect(result).toBe(expectedValue);
});
test('should handle edge case', () => {
expect(() => instance.methodName(null)).toThrow();
});
});
});Factories provide consistent test data with sensible defaults. Import from tests/factories/:
import { createTrackerTask, createTrackerTasks } from '../factories/tracker-task.js';
import { createSessionState } from '../factories/session-state.js';
test('should process task', () => {
// Create with defaults
const task = createTrackerTask();
// Create with overrides
const customTask = createTrackerTask({
id: 'custom-id',
status: 'in_progress',
priority: 1,
});
// Create multiple tasks
const tasks = createTrackerTasks(5, { status: 'open' });
});Mocks simulate external dependencies. Import from tests/mocks/:
import { createMockAgentPlugin, createSuccessfulExecution } from '../mocks/agent-responses.js';
import { createMockChildProcess } from '../mocks/child-process.js';
import { createMockFileSystem } from '../mocks/file-system.js';
test('should execute agent', async () => {
const mockAgent = createMockAgentPlugin();
const mockExecution = createSuccessfulExecution('Task completed');
// Use bun:test mock for module mocking
mock.module('../../src/plugins/agents/registry.js', () => ({
getAgentRegistry: () => ({
getInstance: () => Promise.resolve(mockAgent),
}),
}));
});import { spyOn } from 'bun:test';
test('should call dependency', () => {
const spy = spyOn(dependency, 'method');
instance.doSomething();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith('expected-arg');
});- PRs must have >50% test coverage on new/changed lines (enforced by Codecov patch check)
- Aim for 80%+ line coverage on new code when possible
- Critical paths (engine, plugins) should have higher coverage
- Run
bun test --coverageto check coverage locally - Coverage reports are generated in CI and uploaded to Codecov
Note: If your PR adds new functionality, you must include tests that cover at least 50% of the new/changed lines. PRs that fail the Codecov patch check will not be merged.
For integration testing with actual AI agents:
# Test the TUI
bun run ./src/cli.tsx
# Test specific commands
bun run ./src/cli.tsx run --help
bun run ./src/cli.tsx plugins agents
bun run ./src/cli.tsx config showWhen testing changes manually:
- Test with different trackers (beads, json)
- Test with different agents (claude, opencode)
- Test TUI keyboard navigation
- Test headless mode
- Test error conditions
When reporting issues, please include:
- Ralph TUI version (
ralph-tui --versionor check package.json) - Bun version (
bun --version) - Operating system
- Steps to reproduce
- Expected behavior
- Actual behavior
- Relevant logs (from
.ralph-tui/iterations/)
If you have questions about contributing, feel free to:
- Open a GitHub issue with the
questionlabel - Check existing issues for similar questions
Thank you for contributing to Ralph TUI!