Natural-language linting: write your rules in English, let an LLM enforce them.
Project description
Natural-language linting. Write your rules in English, let an LLM enforce them.
Today: Python source only (*.py), Google Gemini the only provider.
Some conventions are too subjective for a regex or an AST — "comments should explain why, not narrate what the code does," "user-facing errors should explain what went wrong and how to recover." Write those rules in plain English and houserules enforces them with an LLM.
A detector model flags violations; a verifier pass re-checks each one and drops
the false positives. It's advisory by default: reports findings, exits 0,
never blocks. Set mode: strict to exit 1 on findings.
Data, privacy & cost
Each run sends your rule text plus the full numbered source of the files it
checks to Google's Gemini API. With --base, that's the files containing
changed lines — still whole files, not just the changed lines.
On Gemini's unpaid tier, inputs may be used to improve Google's products and may be reviewed by humans; paid-tier inputs are not. Read the Gemini API terms before pointing it at anything sensitive.
Cost and latency are unmeasured so far. Expect roughly a per-PR token estimate in the low thousands and a few seconds to low-tens-of-seconds of latency per run — a ballpark, not a benchmark.
Install
pip install houserules
Create a .env file with a Gemini API key. In a source checkout, start from
the tracked example:
cp .env.example .env
The CLI finds the nearest .env from the current directory upward. An already
exported GEMINI_API_KEY takes precedence over the file.
Configure
Generate a starter .houserules from your repo's own conventions:
houserules init
...or drop one in by hand. houserules needs a .houserules file at your
repo root (JSON or YAML; .houserules.json, .houserules.yaml, and
.houserules.yml also work). See
.houserules.example.yaml
for a full example.
If you pass a single explicit directory path and do not override discovery
with --config, houserules walks from the current directory through that
target and uses the deepest config it finds. That lets houserules examples/basic-demo/ load examples/basic-demo/.houserules.yaml.
model: gemini-3.1-flash-lite
mode: advisory
paths:
- src
rules:
- id: comments-explain-why
description: >
Comments should explain intent, rationale, or non-obvious constraints—not
restate what the code makes clear.
- id: actionable-error-messages
description: >
User-facing error messages should explain what went wrong and how to
recover without exposing implementation details.
Keep mechanically checkable conventions such as TODO markers, import order, and unused variables in Ruff. Use houserules for conventions that require context or judgment.
The examples/basic-demo/ directory shows the same
idea in a runnable miniature. Its README has the exact command and sample
findings.
Run
houserules # lint the configured default paths (verified)
houserules src/ tests/ # lint specific paths
houserules --skip-verification # detector only, no second-pass verifier
houserules --mode strict # exit 1 if anything is found
houserules --base main # lint only lines changed since a git ref
houserules --dry-run # plan work and count model calls, no API calls
Without GEMINI_API_KEY in the environment or a .env file, houserules prints
an error and exits 2 — a missing key is a configuration error, not a quiet
skip. For CI, gate the job on a same-repo if: condition so fork pull requests
(which have no access to the secret) don't fail; see
docs/github-actions.md.
How it works
flowchart LR
A["📝 Rules + 📄 source"] --> B["🔍 Detector"] --> C["✅ Verifier"] --> D["📢 Report"]
- Rules + source go in — your plain-English rules and the files to check.
- Detector flags candidate violations.
- Verifier (on by default) re-checks each one and drops the false positives.
- Report what survives — terminal diagnostics or inline PR annotations.
A surviving finding renders as one path:line:column: rule-id message line:
src/app.py:42:5: comments-explain-why Remove the comment; it restates the assignment below.
(PR annotation screenshot: follow-up.)
Scope to git-changed lines with --base.
Run on pull requests
Lint each PR's changed lines and surface findings as inline annotations on the
diff. Copy-paste setup: docs/github-actions.md.
Full command, flag, config, and exit-code reference: docs/cli.md.
Use as a library
The framework depends only on python-dotenv and PyYAML, and is importable
directly:
from houserules import Rule, StyleLintConfig, run_advisory_lint
Develop
make install # uv sync
make test # pytest
make lint # ruff
Project details
Release history Release notifications | RSS feed
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 houserules-0.1.1.tar.gz.
File metadata
- Download URL: houserules-0.1.1.tar.gz
- Upload date:
- Size: 46.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8d17c661c20209fff23c1cfd7c8020116edbdadf76f5ac89478703f64bb20916
|
|
| MD5 |
80e81336459baa36230c6977dc54468d
|
|
| BLAKE2b-256 |
2b5244cf190e421338857c15fdb3e0e99f18442973fadb6fb84ebb7e5e66dcd2
|
File details
Details for the file houserules-0.1.1-py3-none-any.whl.
File metadata
- Download URL: houserules-0.1.1-py3-none-any.whl
- Upload date:
- Size: 41.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8bd107cdf9042cfb899864ce3981322e4c9a2c1c85edffa7b1d665a2bda60eac
|
|
| MD5 |
9ce6d486729acd0e95939e1503790560
|
|
| BLAKE2b-256 |
06ae38bd4cc93749e11a80bd9e44c1d1832c0b691d520f09763284185910c35c
|