Catch contract drift across documentation, configuration, specifications, and tests.
Project description
RepoInvariant
Alpha: catch contract drift across repository artifacts before merge.
RepoInvariant is a deterministic CLI and GitHub Action that checks whether the contracts spread across your repository still agree. It focuses on two expensive, repeatable failure modes:
- environment variables drifting between
.env.example, Docker Compose, Kubernetes, GitHub Actions, and Spring configuration; - requirement IDs disappearing between Markdown, OpenAPI
x-feature-id, and tests.
It reports exact evidence instead of guessing intent. No API key, LLM, or source upload is required.
Status
RepoInvariant v0.2.0 is a public alpha. The configuration and finding codes may change
before v1.0.0. Pin an exact commit SHA when using the GitHub Action.
GitHub Action
The repository must be checked out before RepoInvariant runs. For supply-chain safety, pin an exact commit SHA:
name: RepoInvariant
on:
pull_request:
permissions:
contents: read
jobs:
contracts:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Check repository contracts
uses: xixvivji/RepoInvariant@17177a536e44f3b8a2de4efe94e241a9849b8bae # v0.1.1
The action installs only the source bundled with the pinned action revision. It does not transmit repository contents or require secrets.
| Input | Default | Description |
|---|---|---|
path |
. |
Repository-relative directory to scan. |
config |
empty | Optional repository-relative configuration file. |
format |
text |
text, json, markdown, or sarif. |
output |
empty | Optional repository-relative report path. |
fail-on |
error |
Blocking threshold: error or warning. |
no-env |
false |
Skip environment-contract checks. |
no-features |
false |
Skip feature-traceability checks. |
Prefer rule-level severity configuration when only one check needs a temporary downgrade.
Quick start
Install the CLI from PyPI:
# uv (recommended)
uv tool install repoinvariant
# pipx
pipx install repoinvariant
# pip (inside an activated virtual environment)
python -m pip install repoinvariant
Then initialize and scan a repository:
cd /path/to/your/repository
repoinvariant init
repoinvariant check .
See it catch drift
The repository includes a passing ticket-service example and an intentionally broken copy. Clone the repository and run both to see the merge gate turn red without configuring a real project first:
$ repoinvariant check examples/ticket-service
PASS: 6 files, 0 errors, 0 warnings
$ repoinvariant check examples/ticket-service-drift
compose.yml:7:25: error ENV001: Environment variable 'HOLD_TTL_SECONDS' is consumed but missing from the contract.
hint: Declare the variable in an environment contract or explicitly ignore it.
openapi.yml:8:21: error TRACE003: Specification feature 'REQ-HOLD-CREATE' has no matching test.
hint: Reference REQ-HOLD-CREATE in a configured test file.
FAIL: 6 files, 2 errors, 0 warnings
The drift example breaks two contracts on purpose: Compose consumes HOLD_TTL_SECONDS without
declaring it in .env.example, and the OpenAPI operation has no matching requirement ID in its
test. Add HOLD_TTL_SECONDS=300 to the environment contract and REQ-HOLD-CREATE to the test to
make it pass.
Configuration
Run repoinvariant init to create .repoinvariant.yml:
version: 1
env:
contracts: [.env.example]
compose: [compose*.yml, docker-compose*.yml]
kubernetes: [k8s/**/*.yml]
workflows: [.github/workflows/*.yml]
spring:
- src/main/resources/application*.yml
- src/main/resources/application*.properties
ignore: [CI, HOME, PATH, GITHUB_*, RUNNER_*]
features:
requirements: [docs/**/*.md]
specifications: [openapi*.yml, docs/openapi*.yml]
tests: [tests/**/*, src/test/**/*]
id_pattern: '\bREQ-[A-Z0-9][A-Z0-9-]*\b'
openapi_extension: x-feature-id
requirements_mode: definitions
ignore: []
rules:
ENV001: error
ENV002: warning
ENV003: warning
TRACE001: error
TRACE002: error
TRACE003: error
TRACE004: warning
requirements_mode: definitions counts IDs only when they look like canonical Markdown
definitions (headings, definition lists, and the first column of tables). Set it to mentions for
legacy repositories where any prose reference is authoritative.
The built-in REQ-* pattern is printed verbatim because it is a constrained public identifier
format. Matches from a custom id_pattern are reported as deterministic custom-id-N labels;
source locations remain exact, but arbitrary matched repository text never reaches logs or report
artifacts.
Each finding code can be set to error, warning, or off. This makes staged adoption explicit:
start a noisy rule as a warning, fix the baseline, then promote it to an error. Quote "off" when
editing YAML to avoid YAML 1.1 boolean parsing surprises.
Then run one of the stable report formats:
repoinvariant check . --format text
repoinvariant check . --format json --output repoinvariant-report.json
repoinvariant check . --format markdown --output repoinvariant-report.md
repoinvariant check . --format sarif --output repoinvariant-report.sarif
Exit code 0 means no blocking drift, 1 means a configured contract failed, and 2 means
the command or configuration was invalid. Add --fail-on warning for a stricter merge gate.
Finding codes
| Code | Meaning | Default severity |
|---|---|---|
ENV001 |
A consumer uses an environment variable absent from the contract | error |
ENV002 |
A contract variable has no discovered consumer | warning |
ENV003 |
Explicit defaults disagree across artifacts | warning |
TRACE001 |
A requirement ID is absent from the specification | error |
TRACE002 |
A specification ID has no requirement | error |
TRACE003 |
A specification ID has no test reference | error |
TRACE004 |
A requirement appears to be defined more than once | warning |
Design boundaries
RepoInvariant deliberately does not:
- decide whether two differently worded requirements mean the same thing;
- modify repository files automatically;
- compare live infrastructure or databases;
- print secret values found in configuration;
- claim full OpenAPI, Compose, Kubernetes, or Spring validation.
Use their native validators alongside RepoInvariant. RepoInvariant owns the gap between artifacts.
Configured files and report destinations must stay inside the repository. RepoInvariant rejects configuration/output symlinks, limits configuration files to 256 KiB and scanned files to 2 MiB, and fails closed on malformed configured YAML. Custom requirement patterns run with a timeout and one shared matching-time budget. File reads and atomic report writes use no-follow directory descriptors so a concurrent symlink swap cannot redirect them outside the repository.
Roadmap
- Environment contract checks
- Requirement → OpenAPI → test traceability
- Text, JSON, Markdown, and SARIF reports
- Composite GitHub Action
- Version-baseline contracts across Gradle, Docker, CI, and documentation
- Reusable parser plugin API
- PyPI trusted publishing and provenance-attested release automation
- Real-world compatibility fixtures from external projects
See CONTRIBUTING.md to help shape future releases. Participation is governed by the Code of Conduct. For usage help, open a question with a minimal synthetic example.
License
Apache License 2.0. See LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file repoinvariant-0.2.0.tar.gz.
File metadata
- Download URL: repoinvariant-0.2.0.tar.gz
- Upload date:
- Size: 109.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c6b31fe0860ed16f497beadbb8b8d276e1ce6ca56b459571c4328644d047d0f4
|
|
| MD5 |
c30ec95b0abe7b897acde8cc4ac5ce79
|
|
| BLAKE2b-256 |
cfc7e5af470574eeea5b7b24faf17b446df54dd321035be6a2f4a0d814604d37
|
Provenance
The following attestation bundles were made for repoinvariant-0.2.0.tar.gz:
Publisher:
release.yml on xixvivji/RepoInvariant
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
repoinvariant-0.2.0.tar.gz -
Subject digest:
c6b31fe0860ed16f497beadbb8b8d276e1ce6ca56b459571c4328644d047d0f4 - Sigstore transparency entry: 2280115492
- Sigstore integration time:
-
Permalink:
xixvivji/RepoInvariant@189ea186c3029763dc92da07599ef977bd6f3868 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/xixvivji
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@189ea186c3029763dc92da07599ef977bd6f3868 -
Trigger Event:
push
-
Statement type:
File details
Details for the file repoinvariant-0.2.0-py3-none-any.whl.
File metadata
- Download URL: repoinvariant-0.2.0-py3-none-any.whl
- Upload date:
- Size: 36.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
73ae0856804d7a686af5a9fb9613a035faa698a9e6881385d2b8cb15bf73a393
|
|
| MD5 |
f1b5fad3576bcea81363f938c0868801
|
|
| BLAKE2b-256 |
f654a575742b7c96b7244673e4990dea55f11e42cce36096be0a3b60c67e986d
|
Provenance
The following attestation bundles were made for repoinvariant-0.2.0-py3-none-any.whl:
Publisher:
release.yml on xixvivji/RepoInvariant
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
repoinvariant-0.2.0-py3-none-any.whl -
Subject digest:
73ae0856804d7a686af5a9fb9613a035faa698a9e6881385d2b8cb15bf73a393 - Sigstore transparency entry: 2280115511
- Sigstore integration time:
-
Permalink:
xixvivji/RepoInvariant@189ea186c3029763dc92da07599ef977bd6f3868 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/xixvivji
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@189ea186c3029763dc92da07599ef977bd6f3868 -
Trigger Event:
push
-
Statement type: