/ CONTRIBUTING.md
CONTRIBUTING.md
1 # Contributing Guide 2 3 Thank you for your interest in contributing to mureo. This guide covers the development setup, coding standards, and PR workflow. 4 5 ## Development Setup 6 7 ### Prerequisites 8 9 - Python 3.10 or later 10 - Git 11 12 ### Clone and Install 13 14 ```bash 15 git clone https://github.com/logly/mureo.git 16 cd mureo 17 18 # Install with dev tools 19 pip install -e ".[dev]" 20 ``` 21 22 ### Verify Installation 23 24 ```bash 25 # Run the test suite 26 pytest tests/ -v 27 28 # Check types 29 mypy mureo/ 30 31 # Check linting 32 ruff check mureo/ 33 ``` 34 35 ## Running Tests 36 37 ### Full Test Suite 38 39 ```bash 40 pytest tests/ -v 41 ``` 42 43 ### With Coverage 44 45 ```bash 46 pytest --cov=mureo --cov-report=term-missing 47 ``` 48 49 **Minimum coverage: 80%.** The CI pipeline will fail if coverage drops below this threshold (configured in `pyproject.toml`). Coverage must stay at or above 80%. 50 51 ### Test Markers 52 53 Tests are categorized with pytest markers: 54 55 ```bash 56 # Unit tests only 57 pytest -m unit 58 59 # Integration tests only 60 pytest -m integration 61 ``` 62 63 ### Test Framework 64 65 - **pytest** with **pytest-asyncio** (async tests auto-detected) 66 - **pytest-mock** for mocking 67 - All API calls must be mocked in tests -- no live API calls in CI 68 69 ### Writing Tests 70 71 Place tests in `tests/` mirroring the source structure: 72 73 ``` 74 mureo/google_ads/client.py → tests/test_google_ads/test_client.py 75 mureo/context/strategy.py → tests/test_context/test_strategy.py 76 ``` 77 78 Example test: 79 80 ```python 81 import pytest 82 from mureo.context import parse_strategy, StrategyEntry 83 84 @pytest.mark.unit 85 def test_parse_strategy_persona(): 86 text = "# Strategy\n\n## Persona\nB2B SaaS buyers.\n" 87 entries = parse_strategy(text) 88 assert len(entries) == 1 89 assert entries[0].context_type == "persona" 90 assert "B2B" in entries[0].content 91 ``` 92 93 ## Coding Standards 94 95 ### PEP 8 96 97 Follow [PEP 8](https://peps.python.org/pep-0008/) conventions. Formatting is enforced automatically. 98 99 ### Type Annotations 100 101 **Required on all function signatures.** mureo uses `mypy --strict`. 102 103 ```python 104 # Good 105 def get_campaign(doc: StateDocument, campaign_id: str) -> CampaignSnapshot | None: 106 ... 107 108 # Bad (missing annotations) 109 def get_campaign(doc, campaign_id): 110 ... 111 ``` 112 113 Use `from __future__ import annotations` at the top of every module for PEP 604 union syntax (`X | Y`). 114 115 ### Frozen Dataclasses 116 117 All data models must use `frozen=True`: 118 119 ```python 120 from dataclasses import dataclass 121 122 @dataclass(frozen=True) 123 class MyModel: 124 name: str 125 value: int 126 ``` 127 128 For fields containing mutable types (`dict`, `list`), use defensive copies in `__post_init__` or convert to immutable types (`tuple` instead of `list`). 129 130 ### Immutability 131 132 Never mutate existing objects. Create new instances instead: 133 134 ```python 135 # Good 136 new_entries = [*entries, new_entry] 137 138 # Bad 139 entries.append(new_entry) 140 ``` 141 142 ### File Size 143 144 - Target: 200-400 lines per file 145 - Maximum: 800 lines 146 - If a file grows beyond this, extract logic into separate modules (see the Mixin pattern used in `google_ads/` and `meta_ads/`) 147 148 ### Formatting and Linting 149 150 ```bash 151 # Format code 152 black mureo/ tests/ 153 154 # Fix auto-fixable lint issues 155 ruff check --fix mureo/ tests/ 156 157 # Type check 158 mypy mureo/ 159 ``` 160 161 Configuration is in `pyproject.toml`: 162 163 - **black**: line-length 88, target Python 3.10 164 - **ruff**: select rules E, F, I, N, W, UP, B, A, SIM, TCH 165 - **mypy**: strict mode 166 167 ### Error Handling 168 169 - Handle errors explicitly. Never silently swallow exceptions. 170 - API client methods should raise `RuntimeError` with user-facing messages. 171 - Log technical details with the `logging` module, not `print()`. 172 173 ### No Hardcoded Secrets 174 175 Never commit credentials, API keys, or tokens. Use environment variables or `~/.mureo/credentials.json`. 176 177 ## Pull Request Guidelines 178 179 ### Before Submitting 180 181 1. **Tests pass**: `pytest tests/ -v` 182 2. **Coverage >= 80%**: `pytest --cov=mureo --cov-report=term-missing` 183 3. **Types pass**: `mypy mureo/` 184 4. **Lint passes**: `ruff check mureo/` 185 5. **Formatted**: `black --check mureo/ tests/` 186 187 ### PR Structure 188 189 - **Title**: concise summary under 70 characters 190 - **Description**: explain *what* and *why*, not just *how* 191 - **Test plan**: describe how the change was tested 192 193 ### Commit Messages 194 195 Follow [Conventional Commits](https://www.conventionalcommits.org/): 196 197 ``` 198 feat: add device performance analysis tool 199 fix: handle empty campaign list in state parser 200 refactor: extract keyword validation to shared helper 201 test: add coverage for Meta Ads rate limit retry 202 docs: update MCP server setup instructions 203 ``` 204 205 ### Adding a New Tool 206 207 When adding a new MCP tool: 208 209 1. **Client method**: Add the async method to the appropriate Mixin in `mureo/google_ads/` or `mureo/meta_ads/`. 210 2. **Tool definition**: Add a `Tool` object to `mureo/mcp/tools_google_ads.py` or `tools_meta_ads.py`. 211 3. **Handler**: Add a handler function and register it in the `_HANDLERS` dict. 212 4. **Tests**: Add unit tests for both the client method and the handler. 213 5. **Documentation**: Update `docs/mcp-server.md` with the new tool. 214 215 ### Adding a New CLI Command 216 217 1. Add the command function to `mureo/cli/google_ads.py` or `mureo/cli/meta_ads.py`. 218 2. Follow the existing pattern: `_require_creds()` -> create client -> `asyncio.run()` -> `_output()`. 219 3. Add tests. 220 4. Update `docs/cli.md`. 221 222 ## Project Structure 223 224 ``` 225 mureo/ 226 ├── mureo/ # Source package 227 │ ├── __init__.py 228 │ ├── auth.py 229 │ ├── google_ads/ 230 │ ├── meta_ads/ 231 │ ├── analysis/ 232 │ ├── context/ 233 │ ├── cli/ 234 │ └── mcp/ 235 ├── tests/ # Test suite 236 ├── docs/ # Documentation 237 ├── pyproject.toml # Project configuration 238 └── README.md 239 ``` 240 241 ## Questions? 242 243 Open an issue on GitHub for questions, bug reports, or feature requests.