๐ค Contributing to ContextGem
July 28, 2026 ยท View on GitHub
Thank you for your interest in contributing to ContextGem! This document provides guidelines and instructions for contributing to the project.
๐ Code of Conduct
Please read our Code of Conduct to understand the expectations for all interactions within the project.
โ๏ธ Contributor Agreement
Before your contribution can be accepted, you must sign our Contributor Agreement. This is a legal document that grants us the necessary rights to use your contribution. The agreement is based on the Oracle Contributor Agreement and this requirement follows OpenSSF Best Practices for silver-level criteria (section "Project oversight").
To sign the agreement:
- Read the Contributor Agreement carefully
- Create a copy of the agreement under
.github/contributors/[your-github-username].md - Fill in all the requested information and include it in your first pull request
๐ค Using AI Coding Assistants
This repository is AI agent-friendly and includes configuration files to help AI coding assistants understand the codebase:
- AGENTS.md - Project overview, architecture patterns, coding conventions, and workflow guidelines for AI assistants (agents.md standard)
- CLAUDE.md - Configuration for Claude Code
When using AI assistants (Claude Code, Cursor, etc.) to contribute:
- Review AI-generated code - Always verify changes follow project patterns and pass tests
- Handle VCR cassettes yourself - AI assistants should not run tests that call LLM APIs without existing cassettes
- Manage git operations yourself - Review and commit changes manually rather than letting AI handle git
๐ก Tip: AI assistants work best when given specific, focused tasks. Break large contributions into smaller pieces for better results.
๐ Getting Started
๐ ๏ธ Development Environment
-
๐ด Fork and clone the repository:
-
First, fork the repository by clicking the "Fork" button on the GitHub project page
-
Then clone your fork to your local machine:
git clone https://github.com/YOUR-GITHUB-USERNAME/contextgem.git cd contextgem- Add the original repository as an upstream remote:
git remote add upstream https://github.com/shcherbak-ai/contextgem.git -
-
โ๏ธ Set up the development environment:
# Install uv if you don't have it pip install uv # Install dependencies and pre-commit hooks uv run fab setup # Or manually: # uv sync --all-groups --upgrade # uv run pre-commit install # uv run pre-commit install --hook-type commit-msg
๐ ๏ธ Available Fabric Commands
The project includes a fabfile.py with common development tasks:
uv run fab --list # List all available commands
uv run fab setup # Set up dev environment (deps + hooks)
uv run fab sync # Sync dependencies with upgrades
uv run fab lint # Run pre-commit checks on all files
uv run fab docs # Build documentation
uv run fab docs-live # Start live documentation server
uv run fab readme # Regenerate README.md from template
uv run fab install-hooks # Install pre-commit hooks
๐ Project Structure
Below is a high-level overview of the codebase layout and where to make different types of contributions:
contextgem/
โ
โโโ contextgem/
โ โ
โ โโโ internal/ # ๐ง Core implementation (start here for new features)
โ โ โโโ base/ # - Core abstractions & business logic
โ โ โ โโโ concepts.py # - Internal concept implementations
โ โ โ โโโ aspects.py # - Internal aspect implementations
โ โ โ โโโ documents.py # - Internal document processing
โ โ โ โโโ llms.py # - Internal LLM functionality
โ โ โ โโโ ... # - More internal implementations
โ โ โโโ prompts/ # - LLM prompt templates
โ โ โโโ typings/ # - Type definitions
โ โ โโโ ... # - More internal modules
โ โ
โ โโโ public/ # ๐ฏ User-facing API (thin facades exposing internals)
โ โโโ concepts.py # - Public concept facades
โ โโโ aspects.py # - Public aspect facades
โ โโโ documents.py # - Public document facades
โ โโโ pipelines.py # - Public pipeline facades
โ โโโ llms.py # - Public LLM facades
โ โโโ ... # - More public modules
โ
โโโ tests/
โ โโโ cassettes/ # ๐ผ VCR recordings (auto-generated)
โ โโโ test_all.py # โ
Feature/integration tests
โ โโโ test_properties.py # ๐ฒ Hypothesis property tests for pure helpers
โ โโโ test_units.py # ๐ฌ Example-based unit tests for pure helpers
โ โโโ utils.py # ๐ ๏ธ Test utilities & dummy env vars
โ โโโ ... # ๐ Test data files
โ
โโโ docs/
โ โโโ source/ # ๐ Documentation source files
โ โโโ ... # ๐ Build configs & outputs
โ
โโโ dev/
โ โโโ usage_examples/ # ๐ Code examples for docs
โ โโโ notebooks/ # ๐ Notebooks (auto-generated)
โ โโโ readme.template.md # โ๏ธ Edit this, not README.md
โ โโโ ... # ๐ ๏ธ Development scripts
โ
โโโ pyproject.toml # โ๏ธ Dependencies & project config
โโโ README.md # ๐ค Auto-generated (don't edit)
๐ฏ Quick Start for Your Contribution:
- Adding new functionality? โ Implement in
contextgem/internal/(core logic). Then expose via a thin public facade incontextgem/public/using the registry. - Writing tests? โ Feature/integration tests go to
tests/test_all.py::TestAll. Property tests for pure helpers go totests/test_properties.py. Example-based unit tests for pure helpers go totests/test_units.py. - Updating docs? โ Edit files in
docs/source/ordev/ - Fixing README? โ Edit
dev/readme.template.md
๐ก Note: Implement functionality in
internal/(base classes, validation, serialization, typing). Usepublic/to expose thin, documented facades that inherit from internal classes and are registered with@_expose_in_registrydecorator to ensure deserialization and instance creation utils return public types. Do not import public classes in internal modules; use the registry for type resolution and publicization.
โ๏ธ Making Changes
-
๐ฟ Create a new branch:
For example:
git checkout -b feature/your-feature-nameWhen creating a branch, use one of the following prefixes that matches your change type:
bugfix/- For bug fixes (e.g.,bugfix/fix-llm-timeout)feature/- For new features (e.g.,feature/add-new-concept-type)breaking/- For breaking changes (e.g.,breaking/concepts-api-v2)docs/- For documentation updates (e.g.,docs/update-aspects-guide)perf/- For performance improvements (e.g.,perf/optimize-prompts)refactor/- For code cleanup or refactoring (e.g.,refactor/simplify-error-handling)
General guidelines:
- Use hyphens (-) between words, not underscores or spaces
- Be specific but concise about what the branch does
- Include issue numbers when applicable (e.g.,
bugfix/issue-42) - Keep branch names lowercase
-
๐ Make your changes following our code style guidelines.
We use several tools to maintain code quality:
- Ruff: For code formatting and linting
- ty: For static type checking
- Bandit: For Python security vulnerability scanning
- Deptry: For dependency health checks (unused, missing, transitive dependencies)
- Interrogate: For docstring coverage checking
- markdownlint-cli2: For Markdown style and consistency
- Twine: For sdist/wheel metadata validation (via the
package-checkpre-commit hook, which runsuv build+twine check) - Pre-commit hooks: To automatically check and format code before commits
The pre-commit hooks will automatically check and format your code when you commit. There are two scenarios to be aware of:
If the hooks modify any files during commit (such as Ruff formatting):
- Review the changes made
- Add the modified files to the staging area
- Commit again
If security issues are detected (Bandit):
- Review the security findings in the terminal output
- Fix the identified security issues in your code
- Add the fixed files to the staging area
- Commit again
-
๐งช Run tests to ensure your changes do not break existing functionality:
uv run pytestNote: We use pytest-recording to record and replay LLM API interactions. Your changes may require re-recording VCR cassettes for the tests. See VCR Cassette Management section below for details.
-
๐พ Commit your changes using Conventional Commits format:
We use Conventional Commits format for our commit messages. Instead of using regular git commit, please use commitizen:
uv run cz commitThis will guide you through an interactive prompt to create a properly formatted commit message with:
- Type of change (feat, fix, docs, style, refactor, etc.)
- Optional scope (e.g., api, cli, docs)
- Short description
- Optional longer description and breaking change notes
Example of resulting commit message:
docs(readme): update installation instructionsNote: If pre-commit hooks fail or modify files during
cz commit, you can retry with the same message:
uv run cz commit --retry
๐ Pull Request Process
-
๐ Update your fork with the latest changes from the
devbranch:git fetch upstream git checkout dev git merge upstream/dev git push origin dev -
๐ค Push your changes to your fork:
git push origin feature/your-feature-name -
๐ฏ Create a pull request from your branch to the
devbranch. We use thedevbranch for integration and testing before merging tomainto keep the main branch stable for releases. -
๐ Fill out the pull request template with all required information.
-
โ๏ธ Sign the Contributor Agreement by including your filled-in
.github/contributors/[your-github-username].mdfile (required in the first pull request). -
โณ Wait for review. Maintainers will review your PR and may request changes.
-
๐ง Address review comments if requested.
๐ Issues and Feature Requests
When submitting issues or feature requests, please use our GitHub issue templates:
-
Check if a similar issue already exists in the Issues section.
-
If not, create a new issue using the appropriate template:
- Bug Report: For reporting bugs or unexpected behavior
- Feature Request: For suggesting new features or enhancements
- Documentation Improvement: For suggesting improvements to our documentation
Each template will guide you through providing all the necessary information for your specific request.
By submitting issues or feature requests to this project, you acknowledge that these suggestions may be implemented by the project maintainers without attribution or compensation.
๐งช Testing
๐๏ธ Current Test Structure
Tests are split across three files:
tests/test_all.py(theTestAllclass) โ feature and integration tests, including all LLM-backed extraction tests that replay through VCR cassettes. Most contributions belong here.tests/test_properties.pyโ Hypothesis-based property tests for pure utility helpers (text cleaning, JSON parsing, type-hint serialization, list chunking, etc.). No LLM, no fixtures, no cassettes.tests/test_units.pyโ example-based unit tests for pure utility helpers (tool-schema generation, type-system normalization, error branches). Pins specific input/output contracts and error messages that property tests aren't well suited to express. No LLM, no fixtures, no cassettes.
Note: We plan to refactor
test_all.pyinto multiple files for better maintainability in the future, but for now feature/integration tests should be added there.
๐ Testing Guidelines
-
Write tests for new features or bug fixes
-
Make sure all tests pass before submitting a PR
-
Maintain code coverage above 80%
-
Check code coverage by running:
uv run pytest --cov=contextgem
๐ฒ Tests for Pure Helpers (Property-Based and Example-Based)
Pure utility helpers in contextgem/internal/ (text cleaning, JSON parsing, type-hint serialization, tool-schema generation, etc.) are covered by two complementary test files. Both run without LLMs, fixtures, or cassettes.
tests/test_properties.py โ property-based tests using Hypothesis. Property tests assert invariants that must hold for any input ("running the function twice yields the same result", "concatenating chunks recovers the original list", "serialize then deserialize round-trips") rather than checking hand-picked input/output pairs. Hypothesis generates many varied inputs and shrinks any failure to a minimal reproducer.
Reach for a property test when:
- You're working with a pure deterministic function with an abstract contract.
- You can describe correctness as one or more invariants (idempotence, round-trip, bounds, length-preservation, etc.).
tests/test_units.py โ example-based unit tests that pin specific input/output contracts. Reach for a unit test when:
- The function's correctness is "this specific output for this specific input" (e.g., "this Python type maps to this JSON schema").
- You need to assert a specific error message or exception type.
- The behavior is contract-shaped rather than invariant-shaped.
Stick with test_all.py for feature/integration scenarios where correctness depends on full document/aspect/concept extraction flows โ most LLM-backed tests fall into this category.
๐ผ VCR Cassette Management
We use pytest-recording to record and replay HTTP interactions with LLM APIs (both cloud-based and local). This allows tests that call LLM APIs to run without making actual API calls after the initial recording.
Note: Tests that do not call LLM APIs do not require or use VCR cassettes. The cassette system only applies to tests that interact with LLM APIs.
Why VCR Cassettes?
VCR cassettes provide the most reliable testing approach for ContextGem because:
- Real API Testing: Testing with actual LLM APIs ensures our functionality works as expected with real responses, edge cases, and API behaviors
- Scalability: With a significant number of LLM API tests, hardcoding requests/responses would be impractical and unmaintainable
- Reproducibility: Once recorded, tests run consistently without variability in LLM responses
- No Setup Friction: Contributors can run tests without API keys or local LLM installations
Local LLMs (Ollama, LM Studio, etc.) also use HTTP APIs (typically on localhost) and their interactions are recorded in cassettes too.
The test suite automatically uses dummy environment variables with pre-recorded cassettes when no .env file is present, so most contributors won't need to set up real API keys or local LLM servers.
Determining Your Scenario
To determine whether you need to record new or re-record existing cassettes, run the tests first:
uv run pytest
Based on the test results and your changes, you'll fall into one of these four scenarios:
โ Scenario 1: No Cassette Recording Required
When this applies:
- New tests that do not call LLM APIs
- Code changes that don't modify internal prompts or LLM parameters
- Changes are compatible with existing pre-recorded API calls (confirmed by passing tests)
What to do:
- Nothing! Tests that call LLM APIs should pass by replaying from existing cassettes with automatically-set dummy environment variables
- No need to create a
.envfile or set up API keys
๐ Scenario 2: New Cassettes Need Recording
When this applies:
- New test methods that call LLM APIs (cloud-based or local)
- Adding tests for new functionality that requires LLM interaction
What to do:
-
Create a
.envfile locally (ignored by git) with the API keys for the LLM services your new tests will use:# Only include the variables for LLM APIs your tests actually call # For OpenAI API tests CONTEXTGEM_OPENAI_API_KEY=your_openai_api_key # For debugging output CONTEXTGEM_LOGGER_LEVEL=DEBUG -
For new LLM providers, create environment variables prefixed with
CONTEXTGEM_:CONTEXTGEM_GOOGLE_AI_STUDIO_API_KEY=your_google_api_key -
Update dummy variables in
tests/utils.pyby adding your new environment variables to thedefault_env_varsdictionary inset_dummy_env_variables_for_testing_from_cassettes(), mapped to a dummy value (e.g. "DUMMY") -
Add the VCR decorator to your new test methods that call LLM APIs (cloud or local):
@pytest.mark.vcr def test_your_new_llm_feature(self): # Your test code that calls LLM APIs (cloud or local)โ ๏ธ Important: Without the
@pytest.mark.vcrdecorator, no cassette will be recorded! -
Run your new tests - new cassettes will be created automatically
-
Verify redaction - check that sensitive data is properly redacted in the new cassette files
-
Test with dummy variables - delete your
.envfile and run tests again to confirm LLM API tests pass by replaying from cassettes with dummy variables
๐ Scenario 3: Some Existing Cassettes Need Re-recording
When this applies:
- Tests fail because your changes are incompatible with specific existing cassettes
- Only certain test cases are affected
What to do:
-
Identify failing cassettes from test output
-
Delete specific cassette files from
tests/cassettes/that need re-recording -
Create a
.envfile if needed (same as Scenario 2) -
Run the affected tests to re-record only the necessary cassettes:
uv run pytest tests/test_all.py::TestAll::test_specific_method
๐๐ Scenario 4: All Cassettes Need Re-recording
When this applies:
- You modified internal prompts (direct changes or code that renders prompts differently)
- You changed default LLM API parameters
- Multiple LLM-related tests fail due to your changes
What to do:
-
Delete all cassette files:
# On Unix/Linux/Mac rm tests/cassettes/*.yaml # On Windows del tests\cassettes\*.yaml -
Create a
.envfile with your API keys (same as Scenario 2) -
Run all tests to re-record everything:
uv run pytest
โ ๏ธ Important: This will use significant API quota and may incur substantial costs!
Environment Variable Security
Automatically Redacted Variables:
CONTEXTGEM_OPENAI_API_KEY
Adding New Variables:
- Use the
CONTEXTGEM_prefix for new API keys - Verify redaction in your cassette files
- Update redaction logic in
tests/utils.pyif needed - Add dummy values to
set_dummy_env_variables_for_testing_from_cassettes()
Local LLM Testing
For local LLM testing, install the following tools and download the relevant models identified under ollama and lm_studio prefixes in tests/test_all.py:
โ ๏ธ Important: Your system needs to have an appropriate GPU capacity to run such local LLMs.
Important Notes
๐ฐ Cost Warning: Recording cassettes for test methods that use live LLM API (non-local LLMs) uses your API keys and will incur charges. Scenario 4 (re-recording all cassettes) can be particularly expensive.
๐ Security: Environment variables such as API keys are automatically stripped from cassettes, but always verify new cassette content.
๐งช Testing: After recording, delete your
.envfile and run tests again to ensure LLM API tests pass by replaying from cassettes with dummy variables.
Network Egress Control
ContextGem uses tethered for network egress control in two complementary places:
1. Production code (tethered.scope) โ the two non-LLM paths the framework controls are wrapped with tethered.scope, which can only narrow (intersection-only) egress and never widens a host application's policy:
contextgem/internal/utils.py::_load_sat_modelโ restricted to HuggingFace hosts (_SAT_MODEL_DOWNLOAD_HOSTS)contextgem/internal/base/llms.py::_calculate_auto_pricing_costsโ restricted toraw.githubusercontent.com(_GENAI_PRICES_REFRESH_HOSTS)
LLM API calls are deliberately not scoped โ endpoints are user-configured (Azure custom domains, OpenAI-compatible proxies, self-hosted, etc.) and are left to the host application's own egress policy.
2. Test suite (tethered.activate) โ enforces egress control at the socket level during VCR-marked tests:
- Replay mode (cassette exists): blocks all outbound connections except HuggingFace (for SaT model downloads not captured by VCR)
- Recording mode (no cassette): allows only approved endpoints (LLM APIs, HuggingFace for model downloads, genai-prices for cost data) and localhost for local LLMs
The fixture activates tethered in hardened mode (locked=True with a private lock token, external_subprocess_policy="block"), so test code can't disable the policy mid-test and non-Python subprocesses are refused โ closing the shell-egress bypass.
The host allowlists in tests/conftest.py are sourced from the production constants (_SAT_MODEL_DOWNLOAD_HOSTS, _GENAI_PRICES_REFRESH_HOSTS) so the test allowlist cannot drift from the per-call production scopes. If you add tests that connect to new endpoints (other than these two), update the _TETHERED_RECORDING_ALLOW list in tests/conftest.py.
๐ Running Tests
Run all tests:
uv run pytest
Run specific tests:
# Run a specific test method
uv run pytest tests/test_all.py::TestAll::test_extract_all
๐ Optional Memory Profiling: For performance testing, you can enable memory profiling to analyze memory usage during test execution:
uv run pytest --mem-profile
Note: Memory profiling adds significant overhead and tests will run much slower when profiling is enabled. Memory profiling helps ensure that ContextGem objects don't consume excessive memory and validates memory usage against defined reasonable limits.
โ ๏ธโ Expected Test Warnings
Warnings generated during tests are often expected and by design. Many warnings are intentionally triggered to test error handling, edge cases, and warning systems. Common expected warnings include:
- LLM extraction errors and retries (testing error handling)
- Missing LLM roles (testing validation logic)
- Concurrency optimization warnings (testing performance comparisons)
- Deprecation warnings from dependencies
Key Point: If tests PASS with warnings, this should not prevent you from submitting your PR. The test suite is designed to handle and expect these warnings as part of normal operation.
๐ Debugging Tests
The log output will show detailed information about test execution.
๐ Documentation
- Update documentation for any changed functionality
- Document new features
- Use clear, concise language
๐๏ธ Building the Documentation
Use the fabric commands from the project root:
For Live Development (Recommended)
# Live rebuild with auto-refresh on file changes
uv run fab docs-live
This starts a development server on http://localhost:9000 with:
- Automatic rebuilds when files change
- Browser auto-refresh
- Pretty URLs without
.htmlextensions
For Static Builds
# Build with verbose output, ignore cache, and treat warnings as errors
uv run fab docs
๐ Viewing the Documentation
With Live Development:
Open http://localhost:9000 in your browser.
With Static Builds:
After building, open docs/build/dirhtml/index.html in your web browser.
๐ Live Documentation
You can access the live documentation at: https://contextgem.dev
Note: Documentation is automatically deployed when maintainers merge changes from
devtomain. As a contributor, your documentation changes will be visible on the live site after your PR is merged and subsequently deployed by maintainers.
๐ Documentation Structure
source/- Contains the source.rstfilessource/_static/- Static assets like imagessource/conf.py- Sphinx configurationbuild/- Generated documentation (not committed to version control)
๐ Updating README.md
The project's README.md is generated from a template dev/readme.template.md, as it embeds code fragments that are located in separate modules and are subject to tests. Do not modify README.md directly as your changes will be overwritten by a pre-commit hook.
Instead:
- Edit the template file at
dev/readme.template.md - The pre-commit hook will automatically update README.md using the template
If you need to test the README generation manually:
uv run fab readme
โ Questions & Support
We're here to help! Whether you're stuck on something technical, have questions about the contribution process, or want to suggest improvements to this guide, don't hesitate to reach out.
๐ Get Help With
- Technical Issues: Setup problems, test failures, or development environment issues
- Contribution Process: Questions about pull requests, branching, or code review
- Feature Ideas: Discussion about new features or improvements
- Documentation: Clarifications about this contributing guide or suggesting improvements
๐ Contact Options
๐ GitHub Issues (preferred for technical questions):
- Open a new issue using our issue templates
๐ง Direct Contact:
- ๐ง Email: sergii@shcherbak.ai
- ๐ผ LinkedIn: Sergii Shcherbak
- ๐ฆ X: @seshch
๐ Improving This Guide
Found something unclear in this contributing guide? Missing information that would have helped you? Please:
- Open an issue with the
documentationlabel - Suggest specific improvements or additions
- Share your contributor experience to help us improve the process
Thank you for contributing to ContextGem! ๐
Your contributions help make ContextGem better for everyone. We appreciate your time and effort!