Skip to main content

oaklint

CI codecov

oak is an opinionated, agent-first Python linter - a cross between Black and Ruff that enforces a curated set of rules targeting common sources of technical debt. These are the patterns that accumulate quietly, from minor style drift up to real structural problems, and they show up most in agent-written code and junior-developer code. oak is built to be both a linter and a learning tool: every rule explains why it exists, so the code gets fixed and the author learns the reasoning behind the fix. Built in Rust on the rustpython-ruff_python_parser crate, so it parses exactly what a modern Python toolchain does while staying a small standalone binary.

oak runs alongside ruff, black, and whatever else is already in your toolchain rather than replacing any of them. It stays fully compatible and layers its curated rules on top, so you keep your existing formatter and linter and add oak for the checks they do not cover.

Some of these rules are hot takes - deliberately more opinionated than a general-purpose linter would risk. In practice I have found they are what keeps medium-to-large teams and their codebases maintainable as they grow.

The set grows by one rule: if a standard can be systematically deduced from the code - checked mechanically rather than by judgment - it gets added here. Anything that needs human taste to adjudicate stays out.

Installation

oak ships as a prebuilt wheel on PyPI, so it installs with no Rust toolchain:

uv tool install oaklint       # install the oak command globally
uvx oaklint path/to/file.py   # or run it without installing
pip install oaklint           # or with pip

The distribution is named oaklint; the installed command is oak.

Usage

oak path/to/file.py src/            # report violations, exit 1 if any
oak --fix src/                      # rewrite files to resolve fixable violations
oak docs OAK005,OAK012              # print the full reasoning for one or more rules

Every rule ships with a full documentation page that an agent or a human can read on demand. Running oak docs <codes> prints the complete rationale, Good/Bad examples, and the recommended fix for exactly those rules - so the reader learns why the rule exists and how to apply the change, without leaving the terminal or hunting through the repo.

Output

A run is structured to be read by an agent under a token budget, not to repeat itself once per line:

Run `oak docs OAK007,OAK014,OAK015` for full rule reasoning, or fetch each individually.

  Code    Count  Rule
  OAK007     10  A public function or class is defined below a private function in the same scope.
  OAK014      3  A function returns a fixed-shape dict literal instead of a named type.
  OAK015      5  A function or method name does not lead with an action verb.
  Total      18

OAK007 - Public function or class must be placed above private functions
  tests/conftest.py:106:5: `build_client`
  tests/conftest.py:126:5: `Ledger`

OAK014 - Function returning a record must use a class, not a dict
  tests/conftest.py:69:9

OAK015 - Function or method name must lead with an action verb
  tests/conftest.py:85:5: `gateway`
  tests/conftest.py:89:5: `ledger`

Found 18 violations

The shape is deliberately agent-friendly and context-length-aware:

  • The docs command leads. One line points at the full reasoning for every rule the run hit, and says each can be fetched on its own - the agent pulls the deep explanation only for the rules it decides to act on, instead of paying for it up front.
  • The summary table amortizes the explanation. Each rule's definition and its violation count appear exactly once, so an agent can triage which rules matter before reading a single location.
  • Locations are grouped, not annotated. The per-rule message is stated once as a section header, then followed by bare path:line:column lines - each suffixed with the specific identifier at fault (a function name, import alias, or offending token) when the rule has one. A file that trips one rule a hundred times costs a hundred short lines, not a hundred repetitions of the same sentence and the same oak docs pointer.

This keeps a large run's output roughly proportional to the number of distinct rules plus the number of locations, rather than to the product of the two - so a sweep over a whole codebase stays inside an agent's context window.

Rules

Code Rule
OAK001 Missing blank line after an indented block (if/for/while/with/try/match).
OAK002 Missing blank line before a return.
OAK003 Comment must be a sentence-case NOTE/TODO/XXX ending with a period.
OAK004 Continuation line must align under the comment's first word.
OAK005 Import must not use an alias.
OAK006 Only functions may be private; classes and module- or class-level names must be public.
OAK007 Private functions must be placed below all public functions and classes in a scope.
OAK008 Name must not be a single character.
OAK009 Test must assert observable behavior, not mock calls.
OAK010 Mock library must not be used, prefer an in-process fake.
OAK011 pytest.raises must not use match=; assert the full error message.
OAK012 Function returning multiple values must use a class, not a tuple.
OAK013 Empty string must not stand for an absent value; use None.
OAK014 Function returning a record must use a class, not a dict.
OAK015 Function or method name must lead with an action verb.
OAK016 Test must not contain conditional logic (if/elif/else).

OAK001 and OAK002 are fixable with --fix, which inserts the missing blank line. OAK003 through OAK015 are report-only. Each rule has a page in docs/rules/ with its rationale and a good/bad example. The rules planned next and the ones left to other tools are in the roadmap.

Continuations (elif, else, except, finally, case) are never flagged, and a guard clause whose block is a single return needs no blank line after it. A comment written directly above a statement belongs to it, so the separating blank line is expected above the whole comment run, not between the comment and its statement.

Configuration

oak reads settings from the first of .oak.toml, oak.toml, or [tool.oak] in pyproject.toml found by walking up from the current directory. A pyproject.toml without a [tool.oak] table is skipped and the search continues upward.

[tool.oak]
select = ["OAK001"]              # when set, only these codes lint (prefixes like "OAK" and "ALL" work)
ignore = ["OAK002"]              # removed from the active set after select
exclude = ["tests/**", "vendor"] # globs skipped entirely
action-verbs = ["yeet", "reconcile"] # extra leading verbs OAK015 accepts

[tool.oak.per-file-ignores]
"tests/**" = ["OAK002"]          # codes silenced only for matching files

In a standalone oak.toml the same keys are written at the top level (no [tool.oak] header). Unknown keys are a hard error and keys are kebab-case.

Inline suppression

A comment can silence a violation in place, following ruff's # noqa model:

import numpy as np  # noak                 # silences every oak rule on this line
import numpy as np  # noak: OAK005         # silences only OAK005 on this line
import numpy as np  # noak: OAK005,OAK008  # silences a comma-separated list

A # oak: noqa comment silences a whole file, with the same optional code list:

# oak: noqa               # silences every oak rule in this file
# oak: noqa: OAK005,OAK008  # silences only these codes in this file

A line directive is anchored to the line the violation is reported on, and the keyword reads case-insensitively (# NOAK). A bare directive with no codes blankets its scope; naming codes narrows it to exactly those.

Default rule set

With no select key, oak runs only the default-on set (see the roadmap) - the low-friction hygiene, formatting, and structural rules OAK001 through OAK008 plus OAK012 (no tuple returns), which almost any team accepts. The opinionated house-style rules stay off until you opt into them.

Enable the opinionated rules by naming them in select - the testing rules (OAK009, OAK010, OAK011), the record-dict rule (OAK014), the empty-string rule (OAK013), and the action-verb rule (OAK015). Setting select replaces the default set, so list every code you want to run, including the default-on ones you want to keep:

[tool.oak]
# NOTE: The default rules plus the action-verb rule.
select = [
    "OAK001", "OAK002", "OAK003", "OAK004", "OAK005",
    "OAK006", "OAK007", "OAK008", "OAK012", "OAK015",
]

select = ["ALL"] runs every rule, including the opinionated and heuristic ones. OAK015 is heuristic and fires against a maintained verb allowlist, so expect to tune it before turning it on broadly.

Development

make check     # fmt --check + clippy -D warnings + tests (the CI gate)
make format    # cargo fmt
make coverage  # per-file source coverage report

make coverage uses Rust's built-in -C instrument-coverage and the system llvm-cov/llvm-profdata, so it needs neither cargo-llvm-cov nor a rustup component. Point it at other target directories or llvm binaries with CARGO_TARGET_DIR, LLVM_COV, and LLVM_PROFDATA, and pass extra flags straight through (make coverage -- --show-missing-lines).

Rules live in src/rules/, one module per rule family. The pipeline is: config::Config::discover (resolve settings) → discovery (find .py files, drop excluded) → linter::check_source (parse + run rules) → filter by select/ignore/per-file-ignoresdiagnostics::Violation (report) → linter::apply_fixes (--fix).

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

oaklint-0.1.0.tar.gz (80.5 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

oaklint-0.1.0-py3-none-win_amd64.whl (1.8 MB view details)

Uploaded Python 3Windows x86-64

oaklint-0.1.0-py3-none-musllinux_1_2_x86_64.whl (1.9 MB view details)

Uploaded Python 3musllinux: musl 1.2+ x86-64

oaklint-0.1.0-py3-none-musllinux_1_2_aarch64.whl (1.8 MB view details)

Uploaded Python 3musllinux: musl 1.2+ ARM64

oaklint-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.8 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

oaklint-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.7 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARM64

oaklint-0.1.0-py3-none-macosx_11_0_arm64.whl (1.7 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

oaklint-0.1.0-py3-none-macosx_10_12_x86_64.whl (1.8 MB view details)

Uploaded Python 3macOS 10.12+ x86-64

File details

Details for the file oaklint-0.1.0.tar.gz.

File metadata

  • Download URL: oaklint-0.1.0.tar.gz
  • Upload date:
  • Size: 80.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for oaklint-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ac6047da9ce178b20391a39f54eb3da10073cbd6b53cab9491e463a4cc5bcd20
MD5 bbf705120d22da3ef00ca6b0de5c6751
BLAKE2b-256 20907defb8508aabbf59eed6abcb5b7ab8e7d09de13fe84249ca2cab2d9abaf1

See more details on using hashes here.

Provenance

The following attestation bundles were made for oaklint-0.1.0.tar.gz:

Publisher: release.yml on omaralikhn/oaklint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file oaklint-0.1.0-py3-none-win_amd64.whl.

File metadata

  • Download URL: oaklint-0.1.0-py3-none-win_amd64.whl
  • Upload date:
  • Size: 1.8 MB
  • Tags: Python 3, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for oaklint-0.1.0-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 a9c6b51a447b2b59f37f9a91992c8aef6a55519a9a54cbaa9d7fd5450730dc76
MD5 e7617231e3dc3082a9e957fa62125947
BLAKE2b-256 d7172c970dfc11c97c7a746a9869f51dcfaf6b43c0431f6aa77c510353731c24

See more details on using hashes here.

Provenance

The following attestation bundles were made for oaklint-0.1.0-py3-none-win_amd64.whl:

Publisher: release.yml on omaralikhn/oaklint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file oaklint-0.1.0-py3-none-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for oaklint-0.1.0-py3-none-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 d35e24f04664ea164c7bd7e872d7ecfe2516cd5492deeb1ce6a00e6454145a3a
MD5 f58922858af155bc9296f58bdf1572c7
BLAKE2b-256 2f86e76e8d73f13ab3e38e6f76db607d5d6a912cc1e48ffb08462e82764014cc

See more details on using hashes here.

Provenance

The following attestation bundles were made for oaklint-0.1.0-py3-none-musllinux_1_2_x86_64.whl:

Publisher: release.yml on omaralikhn/oaklint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file oaklint-0.1.0-py3-none-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for oaklint-0.1.0-py3-none-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 5eec29d5c1c7963e2c934a1504f8bb091796972a2e182babd38953a4f1a17320
MD5 004ff3d24c82f806d1450052ed772700
BLAKE2b-256 02dd8f5c49ce3497653141ea84680f74c128a91ae40ac944bf743722739e3efd

See more details on using hashes here.

Provenance

The following attestation bundles were made for oaklint-0.1.0-py3-none-musllinux_1_2_aarch64.whl:

Publisher: release.yml on omaralikhn/oaklint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file oaklint-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for oaklint-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 099a2d6007614fc990c8b899ec3cd0bfcc98f71fccf780522e0a157222719e05
MD5 d6819a4afcf9612d21df19a8393eef82
BLAKE2b-256 c53df110848e75c86e74b39424979f9ef3cd165a47853c024ce174ad278bbada

See more details on using hashes here.

Provenance

The following attestation bundles were made for oaklint-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on omaralikhn/oaklint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file oaklint-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for oaklint-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 9b7837951c33eb55cb9882ba141382be3f9db8241d57b2cfc22a1d9a49d0d702
MD5 747c3523f08a6014e63c8386dbbc4801
BLAKE2b-256 43f914a5fc7f419996ebd6ea326859ce1cec0679ef3fc12eb4cf765382c780db

See more details on using hashes here.

Provenance

The following attestation bundles were made for oaklint-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on omaralikhn/oaklint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file oaklint-0.1.0-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for oaklint-0.1.0-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 2b42a4335fd35f60f10be223893b81715dadcd8e5dd29047fc2595a4b41ce59a
MD5 6989b4037059e2f6e0adae3a9dd83244
BLAKE2b-256 0d53f21d95864445f9ec269d30423cb1aa8d221529c717c8ffa29bc801ddc3de

See more details on using hashes here.

Provenance

The following attestation bundles were made for oaklint-0.1.0-py3-none-macosx_11_0_arm64.whl:

Publisher: release.yml on omaralikhn/oaklint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file oaklint-0.1.0-py3-none-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for oaklint-0.1.0-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 03f04cffa1ffe394dc7c50708309ac894340a709b36bbfa6e2b3c5557723df4f
MD5 dafe827db523ea7574b7f6ce574671d3
BLAKE2b-256 c1eef68a94e04c117639c278fc9c61be405cad27a6c11ab573b229eb9211b23b

See more details on using hashes here.

Provenance

The following attestation bundles were made for oaklint-0.1.0-py3-none-macosx_10_12_x86_64.whl:

Publisher: release.yml on omaralikhn/oaklint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page