Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

keystones

Force SME review of load-bearing code by pinning a review gate to an AST node instead of a file path.

Mark a function with a one-line comment. A CODEOWNERS-guarded sidecar file records its canonical hash, its source and the reason it matters. Change the function and the hash stops matching, so the only way to get a green build is to edit the sidecar, which puts its owner on the pull request.

CODEOWNERS can only say "someone owns this file", which means protecting one 20-line function also drags its owner into every typo fix in the other 800 lines. That is why those rules get deleted. A keystone protects the function.

Install

pip install keystones          # Python only, zero dependencies
pip install 'keystones[all]'   # adds TypeScript, JavaScript, Go and Terraform

As a pre-commit hook:

repos:
  - repo: https://github.com/KyleJamesWalker/keystones
    rev: v0.1.0
    hooks:
      - id: keystones          # staged files, warns on drift
      - id: keystones-all      # whole repo, blocking
        additional_dependencies: ["tree-sitter-language-pack==1.20.0"]

Pin the grammar pack in your own config, not via this package. The hooks run in an environment pre-commit builds for them, so additional_dependencies fixes the grammar version for your repo without colliding with anything your project itself depends on, and without waiting for a keystones release to move it. The package declares a range; your repo decides the version.

The local hook is advisory: --no-verify skips it. The gate is keystones check --all running in CI, which cannot be skipped. Put .pre-commit-config.yaml in CODEOWNERS, or the gate can be removed by deleting three lines of YAML.

Quickstart

keystones add billing/payout.py::compute_payout \
    --id payout-rounding --category finance \
    -m "GAAP rounding, see the 2026 finance sign-off"

That writes the marker into the source and the sidecar entry:

# keystone(finance): payout-rounding
def compute_payout(amount: Decimal) -> Decimal:
    return amount.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)

Change the rounding mode and keystones check --all fails. Acknowledge it:

keystones fix -m "switched to banker's rounding per policy review"

fix refuses to run without -m when the change is semantic. The resulting sidecar diff contains the old and new source, so the owner reviews code rather than a hash.

Dependencies and staleness

A keystone can name same-repo symbols it depends on, so a change one call frame away is still an owner-review event:

keystones add billing/payout.py::compute_payout --id payout-rounding \
    --category finance -m "GAAP rounding" \
    --depends billing/helpers.py::BASE_RATE \
    --depends billing/helpers.py::quantize

review_every = "180d" sets a staleness budget. There is deliberately no reviewed field: a stored date would be whatever fix last wrote, so the age comes from git log on the sidecar itself. Going stale warns and shows up in keystones list --stale; it never fails the build.

Configuration

[tool.keystones]
root = "keystones"
categories = ["default", "finance"]
exclude = ["**/generated/**"]

One sidecar file per keystone, inside a per-category directory, so each category gets its own reviewers and two concurrent changes can never conflict:

keystones/
  finance/payout-rounding.md
  INDEX.md                     # generated by `keystones index`
# CODEOWNERS
/keystones/finance/  @org/finance-eng

What changing "the code" means

The hash is taken over a canonical rendering of the AST node, not its text.

Change Result
edit outside a region, in the same file passes
ruff format, line rewrap, quote style passes
edit a # comment inside the keystone needs a note, no owner review
change a literal, a call, control flow needs owner review
edit a docstring needs owner review, docstrings are AST nodes
change a symbol listed in depends needs owner review
delete the marker fails until the entry goes too
move the marker onto a different definition fails; the entry records its target
define the same name twice in one file fails; the keystone cannot say which it covers

Markers are found by lexing, so a marker-shaped string literal is not a marker. A marker above a decorator attaches to the function it decorates.

What this is not

A process control, not a security control. Someone who wants around it can delete the marker and the entry in one pull request. That pull request is CODEOWNERS-gated and the deletion is legible in the diff. Known gaps:

  • Indirection. A keystone on compute_payout says nothing about a helper it calls, unless you name that helper in depends. Naming it is opt-in and manual, so the hole is narrowed rather than closed.
  • Copy and repoint. Copying the body to a new unmarked function and repointing callers is undetectable.
  • CODEOWNERS is not self-executing. It requests a reviewer. The block only exists when branch protection requires Code Owner review and dismisses stale approvals. keystones doctor audits that, and needs a token with admin:repo to do it. With no token it skips; with a token it cannot use, it fails rather than reporting success it cannot vouch for.
  • A keystone protects one definition, not a name. It records the target it covers and fails if the marker moves off it, but nothing stops a caller being repointed at different code entirely.

Any file type

Python gets AST granularity. Everything else gets whole-file or region keystones, with no parser and no dependency:

# keystone:start(infra): vpc-peering-cidrs
resource "google_compute_network_peering" "prod" {
  peer_network  = var.peer
  export_routes = true
}
# keystone:end

Editing inside the region trips the gate; editing elsewhere in the file does not. That is the point of regions - a whole-file keystone on a formatter-managed YAML or Terraform file trips on every unrelated edit, which gets the tool uninstalled.

#, //, --, /* */ and <!-- --> all work. Region bodies are compared as normalised text (LF, no trailing whitespace, no runs of blank lines), so a reformat inside a region does trip it. Only the Python adapter is reformat-immune.

A file that documents markers rather than carrying them opts out with a keystones: ignore-file directive anywhere in it. This README has one.

A marker already written into a file is adopted without passing a target:

keystones add --id vpc-peering-cidrs -m "Peering CIDRs are load bearing"

Languages

Language Granularity Reformat-immune
Python function, method, class, test, region, file yes, stdlib ast
TypeScript, TSX, JavaScript function, method, class, interface, type alias, region, file yes, tree-sitter
Go func, method, type, const, region, file yes, tree-sitter
Terraform, HCL block, region, file yes, tree-sitter
everything else region, file no, normalised text

tree-sitter languages need the all extra, which declares a range rather than a pin. The grammar version is recorded in each entry's hasher id, and that, not the install requirement, is what makes hashes deterministic.

A version difference is only reported when it actually matters. On a mismatch the hash is recomputed first: if it still reproduces, the grammar emits the same thing and nothing is said. Only when the two genuinely disagree does it surface, and then as a hasher mismatch rather than as code drift, because from there it is not possible to tell a moved basis from changed code.

keystones fix refuses to write from an environment whose hasher differs from the one an entry records. Without that, running fix with the wrong grammar pack installed would store a hash CI cannot reproduce, and the next check would ask for another fix, forever.

An entry this install cannot verify is an error, not a warning: skipping the hash checks on an unrecognised hasher would make that field a way to switch them off. keystones migrate then moves those entries across, and proves the move rather than asserting it: the stored canonical source is re-rendered under the new hasher, and only when that matches the new hash of the live code does the entry migrate, with no note and no owner review. Where the code changed too, the entry is left alone for the normal gate. A hasher version is a wire format; versions are never removed.

For JavaScript and TypeScript the hash also folds away the things prettier changes on its own: quote style, number spelling (1.50 and 1.5), redundant parentheses, arrow-parameter parens, and a trailing separator. Operators and interior separators are kept, so a + b and a - b differ, and so do [a,,b] and [a,b]. The export keyword and a const/let/var binding are inside the hash, so un-exporting a symbol is a change.

Status

Phase 2 in progress.

Shipped Not yet
C1 orphan marker, C2 orphan entry, C3 semantic drift, C4 comment drift, C5 stored-source integrity, C6 uniqueness, C7 category, C8 CODEOWNERS coverage, C9 removal check, C10 index, C11 dependency drift, C12 staleness call-closure advisory, CI-written reviewed_by
check, fix, add, doctor, list, index, migrate call-closure advisory, CI-written reviewed_by

The hasher is versioned (keystones-ast/1) and treated as a wire format. A pinned-hash test runs on every supported CPython minor, because a hash basis that moves would fail every keystone at once.

License

MIT

Release files for keystones 0.1.0rc1

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

Source distribution (sdist)

Source distribution for keystones 0.1.0rc1
File Size Uploaded
keystones-0.1.0rc1.tar.gz 55.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for keystones 0.1.0rc1
File Interpreter ABI Platform
keystones-0.1.0rc1-py3-none-any.whl Python 3 none any Details

Total release size:102.5 kB

Release files / keystones-0.1.0rc1.tar.gz

Download URL keystones-0.1.0rc1.tar.gz
Size 55.6 kB
Tags Source
SHA-256 checksum
How to use checksums
9aa67e72f9d9e947b1eba69f57bfdf1d228ff5bc039232d8fe735cbacf1ea09b
BLAKE2b-256 checksum
How to use checksums
8039d5dc2859fd548d64aaf44cd5c1a40e91a51532cc8e0bc2eba5803b60d8a0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","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}

Release files / keystones-0.1.0rc1-py3-none-any.whl

Download URL keystones-0.1.0rc1-py3-none-any.whl
Size 46.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
560dac0be24823246484e8c0f145f1728148713a3bdeabba197abcd503c2be1e
BLAKE2b-256 checksum
How to use checksums
23c86d72b78a5824b63809a40b3c4a1efcaecae2a2cdd0375b22370466f63132
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","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}

Release history Release notifications | RSS feed

0.2.0

2 release files

0.1.0

2 release files

This release

0.1.0rc1 This release

2 release 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