Skip to main content

PyPI Python Downloads License keep-the-why-lint (package) ktw-lint Black GitHub Marketplace Read the Docs Telegram X Bluesky Mastodon Keep the Why

Keep the Why — because "ask Bob" is not documentation.

keep-the-why-lint

Keep the Why preserves the reasoning behind your code. keep-the-why-lint checks that it's recorded in a form the next reader can rely on.

keep-the-why-lint is the structural linter for Keep the Why projects — the tests for your context/, in CI after a push and locally right after the agent wrote an entry. Keep the Why is a repo-native convention and agent skill for preserving the reasoning behind a codebase: decisions, rejected alternatives, workarounds, incidents, constraints — the why that code alone can't explain, stored as versioned Markdown in context/. Nothing in that format is enforced the way a compiler enforces correctness. Part of the gap is mechanically closable, though, and that part is this tool's job: required fields, valid values, index consistency, .keep-the-why integrity, hidden-content red flags.

Schema-version-aware: it reads the target project's context-schema and only enforces what that skill version defines — the same "next time touched" philosophy as the skill's own migrations. An unmigrated project never fails on structure its version didn't have.

Runs on: Python 3.10–3.14, no dependencies beyond the standard library — anywhere pip works: GitHub Actions, GitLab CI, pre-commit, or the shell.

Website: https://keepthewhy.com · llms.txt for AI agents/assistants looking up this project

Documentation: Linting · CI linting setup · Finding codes · Migrations · Trust model

How it works

ktw-lint reads .keep-the-why, finds the configured context directory, and validates the machine-checkable half of the schema:

  • Entries — every entry carries Status and Evidence, values come from the documented sets, Type is valid, Verification: contradicted says what contradicts it
  • index.md — exists, every link resolves, every topic file is listed, sorted alphabetically
  • .keep-the-why — required fields present, no field recorded twice, no unknown fields, pinned versions consistent, the configured context/ location exists
  • Hidden content — invisible or directional Unicode is an error, base64-looking blobs a warning: the one mechanically checkable slice of the trust model
  • The developer's setup — with --setup only: the personal file ~/.keep-the-why/<id>.md and the machine-wide ~/.keep-the-why/config get the same field and value checks. Local use, after a settings change; the default run never reads outside the project, and a CI runner has no home files

Every check is gated by the project's context-schema, so a check only fires for a skill version that actually defined it. Fenced code blocks are skipped — example entries in documentation never get linted as real ones.

Exit code 0 clean, 1 findings, 2 usage error. Inside GitHub Actions (GITHUB_ACTIONS set, or --github) findings are emitted as ::error/::warning annotations, so they show up inline on the PR.

Install

pip install keep-the-why-lint

ktw-lint .                 # lint the project in the current directory
ktw-lint /path/to/project  # or any other project root
ktw-lint . --strict        # warnings fail too
ktw-lint . --setup         # also check ~/.keep-the-why/<id>.md and ~/.keep-the-why/config

CI setup

The Keep the Why project init wizard offers to wire the linter into your CI during setup — detected from the repository, never guessed — and CI linting setup has the full detection rules. By hand, these are the same snippets:

GitHub Actions — .github/workflows/ktw-lint.yml. The root of the keep-the-why repository is a composite action (on the GitHub Marketplace) that installs the latest linter from PyPI; the lint-latest tag moves with every linter publish, so there's nothing to pin on your side (pin @lint-v<version> if you want a fixed action revision):

name: ktw-lint

on:
  push:
    branches: [main]
  pull_request:

jobs:
  ktw-lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oliver-zehentleitner/keep-the-why@lint-latest   # rolling; @lint-v<version> pins action and linter together
        with:
          path: "."
          strict: "false"    # "true" turns warnings (e.g. missing Type on old entries) into failures
          # version: "latest"     # only to mix: a pinned ref with a rolling linter, or vice versa — https://keepthewhy.com/linting/#versions-and-pinning

GitLab CI — job for .gitlab-ci.yml:

ktw-lint:
  image: python:3.12
  script:
    - pip install keep-the-why-lint
    - ktw-lint .

pre-commit — hook for .pre-commit-config.yaml (the skill repository root isn't a Python package, so the hook pulls the linter from PyPI):

repos:
  - repo: local
    hooks:
      - id: ktw-lint
        name: keep-the-why-lint
        entry: ktw-lint .
        language: python
        additional_dependencies: ["keep-the-why-lint"]
        pass_filenames: false

strict: "false" / no --strict is the sensible default: warnings (a missing Type on an old entry, a missing guard file) are "next time touched" material per the skill's own rules and shouldn't block a fresh project's CI.

Example

A run against a project with a few problems:

context/auth.md:3: [E102] 'Token refresh window': entry has no **Evidence:** field
context/index.md: [E203] topic file 'auth.md' is not listed in index.md
context/sync.md:11: [W101] 'Retry budget': no **Type:** field — fill it in the next time the entry is touched
context/sync.md:13: [E103] Status 'actve' is not one of: active, superseded, open, needs-review
ktw-lint 0.10.1.2: 3 error(s), 1 warning(s) (context-schema 0.10.1, context: context/)

Every finding code, with its meaning and severity: Finding codes.

Version scheme

Versioned as <schema>.<revision> — e.g. 0.10.1.0. The first three segments are the newest skill schema this release knows every structural gate of; the fourth is the linter's own revision, bumped for linter-only changes (0.10.1.1). Every skill release is preceded by a linter release that knows the new version, even when nothing structural changed, so a project that updates the skill never lints against a linter that doesn't know its context-schema; W003 warns when that happens anyway (a project ahead of the newest published linter), and migrations says whether an update matters.

PEP 440, not strict SemVer — PyPI rejects the build-metadata spelling SemVer would use for this. Releases are tagged lint-v<version> in the repository, and the moving lint-latest tag — the ref the GitHub Action snippet uses — follows the newest one; both are created by the publish workflow only after a successful upload, never by hand.

What this is not

  • Not a content checker. Whether the recorded rationale is true, complete, or honest is not mechanically checkable, and this tool doesn't pretend otherwise — Evidence: confirmed stays a human judgment; the linter only guarantees the field is there and holds a legal value.
  • Not a secret scanner. The hidden-content check catches what needs decoding to be read; pair it with a real scanner (e.g. gitleaks) if you need that.
  • Not a substitute for the skill. It validates what Keep the Why writes; it doesn't write anything itself — see Installation for the skill.

Feedback

Something not working as described, a finding that's wrong, or a structural rule the linter should know about? Open an issue — that's exactly what it's for.

Contributing

Developed in the keep-the-why monorepo under lint/, released independently of the skill. See CONTRIBUTING.md, the Changelog, and the Security policy.

Contributors

Contributors

We ♥️ open source!

License

MIT

Metadata

Release files for keep-the-why-lint 0.16.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for keep-the-why-lint 0.16.0.0
File Size Uploaded
keep_the_why_lint-0.16.0.0.tar.gz 29.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for keep-the-why-lint 0.16.0.0
File Interpreter ABI Platform
keep_the_why_lint-0.16.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 53.0 kB

Release files / keep_the_why_lint-0.16.0.0.tar.gz

Download URL keep_the_why_lint-0.16.0.0.tar.gz
Size 29.6 kB
Tags Source
SHA-256 checksum
How to use checksums
1098575f478d00fcdee6a9c727ddedcdb91c8ba7471ee078b7e08a8ae52ab292
BLAKE2b-256 checksum
How to use checksums
0d0c342efe634851953ea0e7da0b52324cd4b99befb0f4f6683b6902e1ef15b9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.

Transparency log

Release files / keep_the_why_lint-0.16.0.0-py3-none-any.whl

Download URL keep_the_why_lint-0.16.0.0-py3-none-any.whl
Size 23.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2d56ee34fc4e56ca9ec55ad06c479171fb13893672ca6db96ce8e60bc0b43cb6
BLAKE2b-256 checksum
How to use checksums
552a5e9a41b41ce1b4210737049ae17e5f177178ef3a2f9dff5a3d84f0334a8d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.

Transparency log
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page