|
| 1 | +# AGENTS.md - AI Coding Assistant Guidelines |
| 2 | + |
| 3 | +Guidelines for AI coding assistants working with the ContextGem codebase. |
| 4 | + |
| 5 | +For detailed contribution procedures, see `CONTRIBUTING.md`, which covers: |
| 6 | +- Development environment setup |
| 7 | +- Project structure overview |
| 8 | +- VCR cassette recording scenarios |
| 9 | +- Documentation building |
| 10 | + |
| 11 | +## Project Overview |
| 12 | + |
| 13 | +**ContextGem** is a Python LLM framework for extracting structured data from documents using long context windows (not RAG-based). |
| 14 | + |
| 15 | +- **Python**: 3.10-3.13 |
| 16 | +- **License**: Apache 2.0 |
| 17 | +- **Package Manager**: `uv` |
| 18 | + |
| 19 | +## Architecture: Internal/Public Split |
| 20 | + |
| 21 | +The codebase uses a two-layer architecture. **Always implement in internal first, then expose via public.** |
| 22 | + |
| 23 | +``` |
| 24 | +contextgem/ |
| 25 | +├── internal/ # Core implementation (_underscore-prefixed classes) |
| 26 | +│ ├── base/ # Business logic (concepts, aspects, documents, llms) |
| 27 | +│ ├── prompts/ # Jinja2 prompt templates |
| 28 | +│ ├── typings/ # Type system & validators |
| 29 | +│ └── registry.py # Internal-to-public type mapping |
| 30 | +└── public/ # Thin facades (inherit from internal, registered via decorator) |
| 31 | +``` |
| 32 | + |
| 33 | +### Pattern Example |
| 34 | + |
| 35 | +```python |
| 36 | +# 1. Internal implementation (contextgem/internal/base/concepts.py) |
| 37 | +class _StringConcept(BaseModel): |
| 38 | + name: str |
| 39 | + # ... business logic ... |
| 40 | + |
| 41 | +# 2. Public facade (contextgem/public/concepts.py) |
| 42 | +@_expose_in_registry(additional_key=_StringConcept) |
| 43 | +class StringConcept(_StringConcept): |
| 44 | + """Public API documentation.""" |
| 45 | + pass |
| 46 | + |
| 47 | +# 3. Export in contextgem/__init__.py |
| 48 | +``` |
| 49 | + |
| 50 | +## Coding Conventions |
| 51 | + |
| 52 | +| Convention | Rule | |
| 53 | +|------------|------| |
| 54 | +| Internal classes | `_Aspect`, `_Document` (underscore prefix) | |
| 55 | +| Public classes | `Aspect`, `Document` (no prefix) | |
| 56 | +| Constants | `_MAX_NESTING_LEVEL` (ALL_CAPS) | |
| 57 | +| Required import | `from __future__ import annotations` (except `__init__.py`) | |
| 58 | +| Formatter | Ruff (line length: 88) | |
| 59 | +| Type checker | Pyright (basic mode) | |
| 60 | +| Docstrings | reStructuredText format for Sphinx (`:param:`, `:type:`, `:ivar:`, `:vartype:`, `:returns:`, `:rtype:`) | |
| 61 | +| Data models | Pydantic v2 (`BaseModel`, `field_validator`, `model_validator`) | |
| 62 | + |
| 63 | +## Auto-Generated Files - Do NOT Edit |
| 64 | + |
| 65 | +| File | Source | Regeneration | |
| 66 | +|------|--------|--------------| |
| 67 | +| `README.md` | `dev/readme.template.md` | Pre-commit hook runs `dev/populate_project_readme.py` | |
| 68 | +| `docs/source/llms.txt` | Documentation sources | Pre-commit hook | |
| 69 | +| `dev/notebooks/` | `dev/usage_examples/` | Pre-commit hook | |
| 70 | +| `uv.lock` | `pyproject.toml` | `uv sync` | |
| 71 | + |
| 72 | +**To update README**: Edit `dev/readme.template.md`, then run pre-commit or the generation script. |
| 73 | + |
| 74 | +## After Code Changes |
| 75 | + |
| 76 | +**Always run these steps after making changes:** |
| 77 | + |
| 78 | +```bash |
| 79 | +# 1. Run pre-commit hooks (formatting, linting, type checking) |
| 80 | +uv run pre-commit run --all-files |
| 81 | + |
| 82 | +# 2. Run targeted tests for the code you modified |
| 83 | +uv run pytest tests/test_all.py::TestAll::test_relevant_method |
| 84 | + |
| 85 | +# 3. Run full test suite to check for regressions |
| 86 | +uv run pytest |
| 87 | +``` |
| 88 | + |
| 89 | +### Writing Tests for New Implementations |
| 90 | + |
| 91 | +When adding new functionality: |
| 92 | + |
| 93 | +1. **Add test methods** to `tests/test_all.py::TestAll` following existing patterns |
| 94 | +2. **For tests that do NOT call LLM APIs**: Run them to verify they pass |
| 95 | +3. **For tests that call LLM APIs**: Add `@pytest.mark.vcr` decorator, **do NOT run them**, and inform the user that cassettes need recording before these tests can be executed |
| 96 | + |
| 97 | +```python |
| 98 | +# Example: Adding a test in tests/test_all.py |
| 99 | +class TestAll: |
| 100 | + def test_non_llm_feature(self): |
| 101 | + # Safe to run - no LLM calls |
| 102 | + pass |
| 103 | + |
| 104 | + @pytest.mark.vcr # Requires cassette - DO NOT RUN, inform user |
| 105 | + def test_llm_feature(self): |
| 106 | + # Calls LLM API - user must record cassette first |
| 107 | + pass |
| 108 | +``` |
| 109 | + |
| 110 | +### Updating Documentation |
| 111 | + |
| 112 | +When adding or modifying functionality: |
| 113 | + |
| 114 | +1. **Update docstrings** in the affected classes/methods (reStructuredText format) |
| 115 | +2. **Update RST files** in `docs/source/` if the feature has dedicated documentation |
| 116 | +3. **Update `dev/readme.template.md`** if README content is affected (not `README.md` directly) |
| 117 | +4. **Add usage examples** to `dev/usage_examples/` if demonstrating new features |
| 118 | +5. **Verify docs compile** by running from the `docs/` directory: |
| 119 | + |
| 120 | + ```bash |
| 121 | + uv run sphinx-build -b dirhtml source build/dirhtml -v -E -W |
| 122 | + ``` |
| 123 | + |
| 124 | +### VCR Cassette Rules |
| 125 | + |
| 126 | +- **Never run tests in "live" mode** without existing cassettes |
| 127 | +- Tests replay recorded LLM API responses from `tests/cassettes/` |
| 128 | +- If tests fail due to cassette mismatches, **inform the user** that cassettes need re-recording |
| 129 | +- The user will handle cassette re-recording themselves (requires API keys and may incur costs) |
| 130 | +- See [CONTRIBUTING.md - VCR Cassette Management](CONTRIBUTING.md#-vcr-cassette-management) for scenarios |
| 131 | + |
| 132 | +## Git Policy |
| 133 | + |
| 134 | +**Never stage or commit changes** - this is the developer's responsibility. |
| 135 | + |
| 136 | +The developer will review changes and handle git operations themselves. |
| 137 | + |
| 138 | +### File Operations During Refactoring |
| 139 | + |
| 140 | +When moving or renaming files, **always use `git mv`** to preserve git history: |
| 141 | + |
| 142 | +```bash |
| 143 | +# Moving a file |
| 144 | +git mv old/path/file.py new/path/file.py |
| 145 | + |
| 146 | +# Renaming a file |
| 147 | +git mv old_name.py new_name.py |
| 148 | +``` |
| 149 | + |
| 150 | +**Never** use regular file system operations (copy + delete, or IDE rename) for moves/renames - this breaks git history tracking. |
| 151 | + |
| 152 | +## Quick Commands |
| 153 | + |
| 154 | +```bash |
| 155 | +uv sync --all-groups # Install dependencies |
| 156 | +uv run pre-commit run --all-files # Run all linters/formatters |
| 157 | +uv run pytest # Run tests (uses recorded cassettes) |
| 158 | +uv run pytest --cov=contextgem # Run tests with coverage |
| 159 | +uv run pytest tests/test_all.py::TestAll::test_specific # Run specific test |
| 160 | +``` |
| 161 | + |
| 162 | +## Key Gotchas |
| 163 | + |
| 164 | +1. **Never import public classes in internal modules** - use registry for type resolution |
| 165 | +2. **Prompt changes break VCR cassettes** - inform user if tests fail after prompt modifications |
| 166 | +3. **README.md is auto-generated** - edit `dev/readme.template.md` instead |
| 167 | +4. **Never stage or commit** - let the developer handle all git operations |
| 168 | +5. **Always run pre-commit** after code changes before considering work complete |
0 commit comments