Thank you for your interest in contributing to taskowl! This guide will help you get started.
Please be respectful and constructive in all interactions. We're building a welcoming community.
See the README Quick Start for prerequisites, installation, environment variables, and how to run the API server, consumer, and MCP server. Set up your local environment there first, then read the rest of this guide.
-
Create a feature branch:
git checkout -b feature/your-feature-name
-
Make your changes:
- Follow the existing code style
- Add tests for new functionality
- Update documentation as needed
-
Run quality checks:
make check # Runs lint, typecheck, and tests -
Commit your changes:
git add . git commit -m "Add your feature description"
-
Push and create PR:
git push origin feature/your-feature-name
- Formatter: ruff (line length: 100)
- Type checker: ty (strict mode)
- Import sorting: ruff (isort)
- Python version: 3.14+
-
Type annotations: All functions must have type hints
# Good def get_task(task_id: str) -> dict: ... # Bad def get_task(task_id): ...
-
Async/await: Use async for I/O operations
# Good async def fetch_data() -> list[dict]: async with session.execute(query) as result: return result.fetchall() # Bad def fetch_data(): # blocking I/O
-
Error handling: Be explicit about error cases
# Good try: result = await query() except ValueError as e: logger.error(f"Invalid input: {e}") raise # Bad try: result = await query() except: pass
-
Documentation: Document public APIs
def complex_function(param: str) -> dict: """ Brief description of what this function does. Args: param: Description of parameter Returns: Description of return value Raises: ValueError: When param is invalid """
# Run all tests
make test
# Run specific test file
uv run pytest tests/test_queries.py -v- Test location:
tests/directory - Naming:
test_<module>.py - Structure: Use pytest fixtures from
conftest.py
Example test:
@pytest.mark.asyncio
async def test_list_tasks_with_filter(db_session: AsyncSession):
"""Test list_tasks_query with state filter."""
# Arrange
task_id = uuid.uuid4()
db_session.add(TaskEvent(
event_type="succeeded",
task_id=task_id,
timestamp=datetime.now(UTC),
))
await db_session.commit()
# Act
result = await list_tasks_query(state="succeeded", session=db_session)
# Assert
assert len(result) == 1
assert result[0]["id"] == str(task_id)- Queries: Test all query functions with various inputs
- API endpoints: Test all REST endpoints
- Handlers: Test event handlers
- Edge cases: Empty data, invalid inputs, error conditions
- ✅ All tests pass:
make check - ✅ Code is formatted:
uv run ruff format . - ✅ No linting errors:
uv run ruff check . - ✅ Type checking passes:
uv run ty check src/ - ✅ Documentation updated (if needed)
- ✅ Tests added for new functionality
## Description
Brief description of changes
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Testing
Describe how you tested your changes
## Checklist
- [ ] Code follows project style guidelines
- [ ] Self-review completed
- [ ] Tests added/updated
- [ ] Documentation updated
- [ ] All checks pass- Automated checks: CI must pass
- Code review: At least one approval required
- Discussion: Address all review comments
- Merge: Squash and merge to main
- Questions: Open a discussion on GitHub
- Bugs: Open an issue with reproduction steps
- Features: Open an issue to discuss before implementing
By contributing, you agree that your contributions will be licensed under the MIT License.