house-lint
Opinionated Python house-style linter for comments, imports, module layout, and deliberately configured project conventions.
house-lint packages a specific set of checks used across a handful of Python projects. It is intentionally not a general-purpose style framework, an LLM-authorship detector, or a replacement for Ruff.
Quick start
You need Python 3.11 or later.
uvx house-lint check
You see the selected root, configuration, enabled rules, file counts, findings, and a final summary. The default scan looks for src, tests, scripts, tools, and examples under the project root.
Install it in a project when you want a pinned development dependency:
uv add --dev house-lint
uv run house-lint check
Rules
The four default rules are HSL001–HSL004. HSL900 always reports invalid suppressions.
| ID | Default | Checks |
|---|---|---|
HSL001 |
On | AI-writing tells in comments and docstrings, never ordinary strings |
HSL002 |
On | Imports inside function or async-function bodies |
HSL003 |
On | Top-level TYPE_CHECKING guards followed by imports |
HSL004 |
On | Module constants after the first class/function, except derived bindings |
HSL101 |
Off | Configured planning/spec tokens in comments, docstrings, and filenames |
HSL102 |
Off | Files whose splitlines() count exceeds a configured limit |
HSL103 |
Off | Bound exception names outside the allowed policy |
HSL900 |
Always | Invalid, unused, conflicting, or malformed suppressions |
List the installed rule metadata:
house-lint rules --format json
The command writes a schema-versioned JSON object containing all eight IDs and their enablement modes.
See the rule reference for exact rule behavior.
Configuration
Put configuration in your project pyproject.toml:
[tool.house-lint]
include = ["src", "tests"]
exclude = ["generated/"]
select = ["HSL001", "HSL002", "HSL003", "HSL004", "HSL101", "HSL102", "HSL103"]
[tool.house-lint.rules.HSL102]
max_lines = 800
[tool.house-lint.rules.HSL103]
allowed = ["exc", "*_exc"]
[[tool.house-lint.rules.HSL101.tokens]]
prefixes = ["AC", "FR", "NFR", "WP"]
hash = "optional"
min_digits = 1
suffix = "optional-lower-alpha"
scopes = ["comments", "docstrings", "filenames"]
case_sensitive = true
HSL101 requires at least one token family whenever you select it. HSL102 defaults to 800 lines and HSL103 defaults to exc or *_exc when selected.
CLI selection overrides configuration:
house-lint check --select HSL002,HSL103 --ignore HSL103
Each --select or --ignore occurrence accepts one comma-separated list. Selection is strict: unknown, duplicate, empty, and HSL900 IDs are usage errors.
Read configuration for discovery, precedence, validation, excludes, and token-family options.
Paths, roots, and Git ignores
With no paths, check scans configured include roots. With paths, it scans only those explicit Python files or recursively expanded explicit directories:
house-lint check src/service.py tests
Explicit paths are strict. Missing, out-of-root, and non-Python file arguments are errors; ignored or excluded explicit Python files are counted as skipped. --root fixes the project boundary and only considers <root>/pyproject.toml. Without --root, discovery starts at the current directory. --config selects an exact configuration file; without --root, its parent becomes the root.
The linter loads only the selected root's .gitignore, plus built-in and configured excludes. It does not search nested .gitignore files or shell out to Git. Use --no-gitignore to disable only the root .gitignore.
Suppressions
Suppress a finding only with a rule ID and a meaningful reason (at least three alphanumeric characters):
def load_plugin():
import plugin # house-lint: ignore[HSL002] - avoids a circular import
# house-lint: ignore-next[HSL103] - compatibility callback signature
try:
callback()
except OSError as error:
raise
# house-lint: ignore-file[HSL102] - generated compatibility module
ignore attaches to its containing statement. A comment-only ignore-next attaches to the next statement in the same suite, even across ordinary comments and blank lines. A top-of-file ignore-file applies to the listed enabled rules throughout the file. HSL900 cannot be suppressed.
Read suppressions before adding one; malformed, misplaced, disabled, unknown, unused, duplicate, and conflicting pragmas produce HSL900.
Pre-commit
Run the installed CLI from a local pre-commit hook:
repos:
- repo: local
hooks:
- id: house-lint
name: house-lint
entry: house-lint check
language: system
types: [python]
files: \.py$
The distributed .pre-commit-hooks.yaml exposes the same house-lint hook for a published repository. Both forms filter to existing *.py files before invocation. The CLI remains strict, so manual explicit paths still fail for missing or non-Python files.
Output and exits
Use JSON for machine consumers:
house-lint check --format json
JSON stdout is always one parseable schema-version-1 object. It always includes root, config, enabled_rules, file counts, findings, errors, and summary counts. root and config are absolute strings when available and null otherwise; filename and file-level findings have all location fields set to null.
Finding message values are human-readable display text, not stable machine keys. Machine consumers should use rule IDs and locations for findings, and error code values for operational failures.
| Exit | Meaning |
|---|---|
| 0 | Complete scan with no visible findings or errors |
| 1 | Complete scan with lint findings, including HSL900 |
| 2 | CLI usage or configuration error; scanning did not start |
| 3 | Incomplete scan: path, traversal, budget, read, decode, tokenize, or syntax error |
| 4 | Unexpected internal error caught at the CLI boundary |
Exit precedence is 4 > 3 > 2 > 1 > 0. In JSON mode, diagnostics stay in the JSON result on stdout; --debug writes additional details only to stderr.
errors[*].code is the stable machine-readable error taxonomy. kind, phase, and operation provide context and may gain new values without changing an existing error code.
| Code | Meaning |
|---|---|
config-error |
CLI argument or configuration loading failure |
path-error |
Invalid root, explicit path, or selected source path |
traversal-error |
Discovery or root .gitignore filesystem failure |
budget-error |
Discovery or candidate-count safety limit exceeded |
source-too-large |
A selected source file exceeds the 10 MiB read limit |
read-error |
A selected source file could not be read |
decode-error |
A selected source file could not be decoded |
tokenize-error |
A selected source file could not be tokenized |
syntax-error |
A selected source file could not be parsed as Python |
internal-error |
An unexpected failure crossed the CLI boundary |
Development
uv run pytest
uv run ruff check .
uv run pyright
See the changelog for compatibility notes.
Releases
CI tests Python 3.11 through 3.14 and runs Ruff, Pyright, and a package build. Release Please manages version bumps, changelog updates, and GitHub releases from Conventional Commits. Distributions are published to PyPI via Trusted Publishing.
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 house_lint-0.1.1.tar.gz.
File metadata
- Download URL: house_lint-0.1.1.tar.gz
- Upload date:
- Size: 26.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6a1cb104497f3a17d3e3500184376bd8bba9873496162164809ae0dd072b6817
|
|
| MD5 |
8c41a7e87d6df1c05b0f0c48d6e5e92b
|
|
| BLAKE2b-256 |
4b62172e10cc1288b7ff3ff565098fb909b76459f367a742bb7698f8caff34c3
|
Provenance
The following attestation bundles were made for house_lint-0.1.1.tar.gz:
Publisher:
release-please.yml on NodeJSmith/house-lint
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
house_lint-0.1.1.tar.gz -
Subject digest:
6a1cb104497f3a17d3e3500184376bd8bba9873496162164809ae0dd072b6817 - Sigstore transparency entry: 2426798177
- Sigstore integration time:
-
Permalink:
NodeJSmith/house-lint@db001fb0bb8d6f5032f4c7b5798c6ebfc69834b1 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/NodeJSmith
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-please.yml@db001fb0bb8d6f5032f4c7b5798c6ebfc69834b1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file house_lint-0.1.1-py3-none-any.whl.
File metadata
- Download URL: house_lint-0.1.1-py3-none-any.whl
- Upload date:
- Size: 35.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
00b12fa5a5dc0d517667ef43fc986a168f76381f0396f28042ea46ebae21e4fc
|
|
| MD5 |
f5458e31438cd647beb45ea59c00a43a
|
|
| BLAKE2b-256 |
0fdf228881ab92f2b6b0c16878fb08565dc6296280e36b5d4c79ac4830d27c34
|
Provenance
The following attestation bundles were made for house_lint-0.1.1-py3-none-any.whl:
Publisher:
release-please.yml on NodeJSmith/house-lint
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
house_lint-0.1.1-py3-none-any.whl -
Subject digest:
00b12fa5a5dc0d517667ef43fc986a168f76381f0396f28042ea46ebae21e4fc - Sigstore transparency entry: 2426798243
- Sigstore integration time:
-
Permalink:
NodeJSmith/house-lint@db001fb0bb8d6f5032f4c7b5798c6ebfc69834b1 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/NodeJSmith
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-please.yml@db001fb0bb8d6f5032f4c7b5798c6ebfc69834b1 -
Trigger Event:
push
-
Statement type: