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 CI linter for Keep the Why projects — the tests for your context/. 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

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

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

Download files

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

Source Distribution

keep_the_why_lint-0.13.3.0.tar.gz (26.2 kB view details)

Uploaded Source

Built Distribution

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

keep_the_why_lint-0.13.3.0-py3-none-any.whl (21.2 kB view details)

Uploaded Python 3

File details

Details for the file keep_the_why_lint-0.13.3.0.tar.gz.

File metadata

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

File hashes

Hashes for keep_the_why_lint-0.13.3.0.tar.gz
Algorithm Hash digest
SHA256 d81793bfaa7af5edb9ba9bc7c6fefd926950f61a0dec19f0b3c4d99fd8551c43
MD5 feaf7798e47e2e311785ea0a8b08ee2b
BLAKE2b-256 99775595a7dd0862a57584bdb63528085a5a136c83f0290379f8e2952c782cc2

See more details on using hashes here.

Provenance

The following attestation bundles were made for keep_the_why_lint-0.13.3.0.tar.gz:

Publisher: publish-lint.yml on oliver-zehentleitner/keep-the-why

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

File details

Details for the file keep_the_why_lint-0.13.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for keep_the_why_lint-0.13.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1e5a8bff1467b2e32cdd8535fb79b63eac0db683913ecda3f7a6aca5e7cda5da
MD5 a6e34cf261a66ab756ec7441e9329894
BLAKE2b-256 960e57dda126c41b3cc2bc3c7acc82f28654a96d5caf45af2ff14cc162d7e9bd

See more details on using hashes here.

Provenance

The following attestation bundles were made for keep_the_why_lint-0.13.3.0-py3-none-any.whl:

Publisher: publish-lint.yml on oliver-zehentleitner/keep-the-why

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

Release history Release notifications | RSS feed

0.15.0.0

2 files

0.14.1.0

2 files

0.14.0.0

2 files

This release

0.13.3.0 This release

2 files

0.13.2.0

2 files

0.13.1.0

2 files

0.13.0.0

2 files

0.12.0.0

2 files

0.11.0.0

2 files

0.10.1.2

2 files

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