/ 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.