From 53f27fb7dbbaef4246374b7c08811b61dbb1551c Mon Sep 17 00:00:00 2001
From: Jeff Dickey <216188+jdx@users.noreply.github.com>
Date: Sun, 6 Sep 2026 00:08:55 -0500
Subject: [PATCH 1/3] docs: overhaul guides and redesign the documentation site
Entire-Checkpoint: 01M1THX7DZ2NKY3073EFHRHNPZ
---
AGENTS.md | 39 +-
CONTRIBUTING.md | 16 +-
README.md | 111 +--
bin/generate_docs.rs | 4 +-
docs/.vitepress/config.mts | 97 +-
docs/.vitepress/sidebar.ts | 110 ++-
docs/.vitepress/theme/HomePage.vue | 921 ++++++++++--------
docs/.vitepress/theme/Layout.vue | 133 +--
docs/.vitepress/theme/banner.css | 4 +-
docs/.vitepress/theme/index.ts | 76 +-
docs/.vitepress/theme/style.css | 503 +++-------
docs/about.md | 40 +-
docs/benchmarks.md | 125 +--
docs/builtins.md | 120 +--
docs/ci.md | 88 ++
docs/cli/agent.md | 21 +
docs/cli/agent/hooks.md | 20 +
docs/cli/agent/instructions.md | 20 +
docs/cli/agent/mcp.md | 20 +
docs/cli/builtins.md | 19 +
docs/cli/cache/clear.md | 20 +
docs/cli/check.md | 30 +-
docs/cli/commands.json | 192 ++--
docs/cli/completion.md | 21 +
docs/cli/config.md | 21 +
docs/cli/config/dump.md | 20 +
docs/cli/config/explain.md | 20 +
docs/cli/config/get.md | 20 +
docs/cli/config/sources.md | 20 +
docs/cli/fix.md | 31 +-
docs/cli/index.md | 27 +-
docs/cli/init.md | 23 +
docs/cli/install.md | 22 +
docs/cli/mcp.md | 19 +
docs/cli/migrate.md | 20 +
docs/cli/migrate/pre-commit.md | 20 +
docs/cli/run.md | 31 +-
docs/cli/run/commit-msg.md | 19 +-
docs/cli/run/post-checkout.md | 19 +-
docs/cli/run/post-commit.md | 19 +-
docs/cli/run/post-merge.md | 19 +-
docs/cli/run/post-rewrite.md | 19 +-
docs/cli/run/pre-commit.md | 28 +-
docs/cli/run/pre-push.md | 19 +-
docs/cli/run/pre-rebase.md | 19 +-
docs/cli/run/prepare-commit-msg.md | 19 +-
docs/cli/sponsors.md | 19 +
docs/cli/test.md | 21 +
docs/cli/uninstall.md | 20 +
docs/cli/util.md | 20 +
docs/cli/util/check-added-large-files.md | 19 +
docs/cli/util/check-byte-order-marker.md | 19 +
docs/cli/util/check-case-conflict.md | 19 +
docs/cli/util/check-conventional-commit.md | 19 +
.../util/check-executables-have-shebangs.md | 19 +
docs/cli/util/check-merge-conflict.md | 19 +
docs/cli/util/check-symlinks.md | 19 +
docs/cli/util/detect-private-key.md | 19 +
docs/cli/util/end-of-file-fixer.md | 20 +
docs/cli/util/fix-byte-order-marker.md | 19 +
docs/cli/util/fix-smart-quotes.md | 20 +
docs/cli/util/mixed-line-ending.md | 20 +
docs/cli/util/no-commit-to-branch.md | 19 +
docs/cli/util/python-check-ast.md | 19 +
docs/cli/util/python-debug-statements.md | 19 +
docs/cli/util/trailing-whitespace.md | 20 +
docs/cli/validate.md | 20 +
docs/cli/version.md | 19 +
docs/configuration.md | 518 ++++------
docs/contributing.md | 113 ++-
docs/environment_variables.md | 415 +++-----
docs/getting_started.md | 188 ++--
docs/glossary.md | 122 +--
docs/hooks.md | 215 ++--
docs/index.md | 4 +-
docs/logging.md | 224 ++---
docs/mise_integration.md | 140 +--
docs/pkl_introduction.md | 233 +++--
docs/public/custom-linters.pkl | 117 +--
docs/public/javascript-project.pkl | 35 +-
docs/public/llms.txt | 50 +-
docs/public/monorepo.pkl | 42 +-
docs/public/python-project.pkl | 57 +-
docs/reference/examples/custom-linters.md | 176 +---
docs/reference/examples/index.md | 28 +-
docs/reference/examples/javascript-project.md | 89 +-
docs/reference/examples/monorepo.md | 168 +---
docs/reference/examples/python-project.md | 107 +-
docs/shanty.md | 15 +-
docs/why-hk.md | 137 +--
hk.usage.kdl | 8 +-
mise-tasks/docs/build | 1 +
mise-tasks/render/usage | 1 +
pkl/Config.pkl | 82 +-
scripts/check-docs-links.py | 62 ++
scripts/enrich-cli-docs.py | 157 +++
scripts/generate-examples.sh | 66 +-
settings.toml | 102 +-
src/hook_options.rs | 9 +-
test/README.md | 102 +-
100 files changed, 3736 insertions(+), 3698 deletions(-)
create mode 100644 docs/ci.md
create mode 100644 scripts/check-docs-links.py
create mode 100644 scripts/enrich-cli-docs.py
diff --git a/AGENTS.md b/AGENTS.md
index 3a3a3eaf9..ed8ff3f69 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -2,7 +2,7 @@
## mbx build cache
-`mise install` installs mbx 1.6. `mise run` activates the project's transparent
+`mise install` installs the configured mbx version. `mise run` activates the project's transparent
Cargo wrapper, so compilation-heavy mise tasks and hk checks use ordinary
`cargo` commands. Standalone Cargo commands require an activated mise shell. If
the wrapper fails or creates a development papercut, rerun the exact equivalent
@@ -24,6 +24,7 @@ SHOULD use the same format:
**Format:** `(): `
**Types:**
+
- `feat:` - New features
- `fix:` - Bug fixes that affect the CLI behavior (not CI, docs, or infrastructure)
- `refactor:` - Code refactoring
@@ -37,15 +38,18 @@ SHOULD use the same format:
- `revert:` - Reverting a previous change
**Scopes:**
+
- For command-specific changes, use the command name: `check`, `fix`, `run`, `init`, `install`, `validate`, etc.
- For subsystem changes: `hook`, `step`, `config`, `lock`, `pkl`, `builtins`, `stash`, `deps`
**Description Style:**
+
- Start the description with a lowercase character
- Use imperative mood ("add feature" not "added feature")
- Keep it concise but descriptive
**Examples:**
+
- `fix(step): resolve race condition in file locking`
- `feat(check): add --slow flag for expensive linters`
- `feat(builtins): add biome linter`
@@ -66,11 +70,13 @@ imperative mood remains a review rule.
## Development Commands
**Build the project:**
+
```bash
mise run build
```
**Run tests:**
+
```bash
# Run all tests (Rust unit tests + bats integration tests)
mise run test
@@ -89,6 +95,7 @@ mise run test:bats test/check.bats
```
**Lint and format code:**
+
```bash
# Run all linters and checks
hk check --all
@@ -103,28 +110,32 @@ hk fix --all --slow
hk is a git hook manager and project linting tool written in Rust with emphasis on performance and concurrent execution. The architecture leverages file locks to maximize concurrency while preventing race conditions.
-### Workspace Structure
+### Crate Structure
+
+The root Cargo package builds the **hk** CLI and the **generate-docs** utility.
+It depends on separately published crates for shared functionality:
-The project is a Cargo workspace with these crates:
-- **hk** (root): Main CLI application
-- **xx**: HTTP client and utility library
+- **xx**: HTTP client and utilities
- **clx**: CLI/terminal UI utilities (progress indicators, styling)
- **ensembler**: Script/command execution engine
### Core Components
**Configuration System (src/config.rs):**
+
- Main config file: `hk.pkl` in project root
- Uses Pkl (github.com/apple/pkl) as the configuration language
- Config amends a base schema from `pkl/Config.pkl`
**Hook System (src/hook.rs):**
+
- Manages git hooks (pre-commit, pre-push, commit-msg, prepare-commit-msg)
- Supports custom hooks like "check" and "fix" for manual runs
- Implements stashing strategies for git hooks
- Handles concurrent step execution with proper locking
**Step Execution (src/step/):**
+
- Steps are individual linting/formatting tasks
- Each step can have: check, fix, shell commands
- Steps support glob patterns for file filtering
@@ -132,25 +143,35 @@ The project is a Cargo workspace with these crates:
- Steps use read/write file locks to prevent conflicts
**File Locking (src/file_rw_locks.rs):**
+
- Implements a sophisticated file locking system
- Allows multiple readers or single writer per file
- Prevents race conditions during concurrent execution
- Critical for maximizing parallelism
**Built-in Linters (pkl/builtins/):**
+
- Extensive library of pre-configured linters and formatters
- Each builtin is a Pkl file defining step configuration
- Used via `Builtins.linter_name` in hk.pkl
**CLI Interface (src/cli/):**
+
- Subcommands: init, install, uninstall, check, fix, run, validate, config
-- Uses clap for argument parsing
+- Uses usage-rs for argument parsing
- Supports running specific hooks or steps
+### Documentation
+
+- Preview or build the website with `mise run docs` or `mise run docs:build`.
+- Edit generated reference content at its source: `pkl/Config.pkl`, `settings.toml`, builtin definitions, and Rust CLI help comments.
+- `scripts/enrich-cli-docs.py` adds maintained examples after CLI reference generation.
+- Example pages include `docs/public/*.pkl` directly. Validate them with `scripts/generate-examples.sh` in the mise environment.
+
### Key Design Patterns
1. **Concurrent Execution:** Steps run in parallel when possible, using tokio for async runtime
-2. **File-based Coordination:** Uses file locks instead of in-memory coordination for cross-process safety
+2. **File-based Coordination:** Uses in-memory read/write locks keyed by file path to coordinate steps within a hook run
3. **Pluggable Configuration:** Pkl-based config allows easy extension and customization
4. **Progressive Enhancement:** Works with or without git, libgit2, mise, etc.
@@ -163,12 +184,14 @@ The project is a Cargo workspace with these crates:
### Testing
Bats integration tests are in `test/*.bats`. Each test file uses a common setup pattern:
+
```bash
setup() {
load 'test_helper/common_setup'
_common_setup
}
```
+
Tests run in isolated temp directories with a clean git repo. The `$PKL_PATH` variable points to the pkl config directory for amending `Config.pkl`.
#### Testing Builtins
@@ -176,6 +199,7 @@ Tests run in isolated temp directories with a clean git repo. The `$PKL_PATH` va
Builtins should have pkl-level tests defined via the `tests` field on the Step (see `pkl/Config.pkl` `StepTest`). These tests are run by `hk test` and exercised in CI via `test/builtins_tests.bats`, which loads all builtins and runs their tests.
**Tool stubs** in `test/builtin_tool_stubs/` use `mise tool-stub` to auto-install the correct tool version on demand. Each stub is a small script:
+
```bash
#!/usr/bin/env -S mise tool-stub
version = "2"
@@ -183,6 +207,7 @@ tool = "aqua:golangci/golangci-lint"
```
To add a new builtin with tests:
+
1. Define the builtin in `pkl/builtins/.pkl` with a `tests` block
2. Add a tool stub in `test/builtin_tool_stubs/` if the tool isn't already available
3. Use the `TestMaker` helper from `pkl/builtins/test/helpers.pkl` for standard check/fix test patterns
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9540019a1..296a8ff5c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,6 +1,18 @@
-# Contributing
+# Contributing to hk
-See the [contributing guide](https://hk.jdx.dev/contributing).
+Read the [contributing guide](docs/contributing.md) for review expectations, development setup, and how to add a builtin. It is also available on the [documentation website](https://hk.jdx.dev/contributing).
+
+To get a checkout ready:
+
+```sh
+mise install
+mise run build
+mise run test
+```
+
+For a documentation change, use `mise run docs` to preview the site and `mise run docs:build` to validate it. Generated reference content has its own source files; see [editing documentation](docs/contributing.md#edit-documentation).
+
+PR titles must use Conventional Commits; use the same format for intermediate commits where practical, for example `fix(step): handle missing files` or `docs: clarify hook installation`.
## mbx build cache
diff --git a/README.md b/README.md
index 9c0110415..f6cd6bc59 100644
--- a/README.md
+++ b/README.md
@@ -1,63 +1,39 @@
# hk
-A fast, language-agnostic git hook manager and project linter.
+**Git hooks and project checks, in parallel.**
-hk runs linters concurrently while coordinating access to files with read/write locks. This lets
-formatters and other tools safely work on overlapping files without racing or silently overwriting
-one another's changes.
+hk runs linters and formatters with read/write file locks, so independent work runs concurrently and tools that modify the same files take turns. Use the same steps in Git hooks, from your terminal, and in CI.
-- Runs independent checks and fixes in parallel
-- Safely handles partially staged files by stashing and restoring unstaged changes
-- Includes [built-in configurations](https://hk.jdx.dev/builtins) for common linters and formatters
-- Uses typed [Pkl](https://pkl-lang.org/) configuration
-- Integrates with [mise](https://mise.jdx.dev/) for tool and task management
-- Provides fast native checks for common issues such as trailing whitespace and merge conflicts
+[Get started](https://hk.jdx.dev/getting_started) · [Documentation](https://hk.jdx.dev/) · [Built-in linters](https://hk.jdx.dev/builtins) · [CLI reference](https://hk.jdx.dev/cli/)
## Quick start
-From the project you want to configure, install hk with mise:
+Install with [mise](https://mise.jdx.dev/), then run these commands inside your repository:
```sh
-cd my-project
mise use hk
-hk --version
-```
-
-With Git 2.54 or newer, install hk's hooks once for every repository on your machine:
-
-```sh
-hk install --global
-```
-
-Then enable hk in a project:
-
-```sh
hk init
+hk install
+hk check --all
```
-`hk init` detects relevant linters and creates an `hk.pkl` configuration. Review the detected
-linters, or use `hk init --interactive` to select them yourself. You can then commit as usual; hk
-runs the configured `pre-commit` hook automatically. Repositories without an `hk.pkl` are left
-untouched by the global hooks.
+`hk init` detects project tools and generates `hk.pkl`. Review the selected linters and make sure their executables are available on `PATH`; hk configures how to run them, but does not install them. Use `hk init --interactive` to choose tools yourself.
-On older Git versions, run `hk install` in each project instead. See the
-[getting started guide](https://hk.jdx.dev/getting_started) for Homebrew, Cargo, and Aqua installation
-options and detailed hook setup.
+On Git 2.54+, you can run `hk install --global` once to enable hk across repositories. Installed hooks exit silently in projects without an hk configuration. See [installation options](https://hk.jdx.dev/getting_started#install-hooks) for older Git versions and mise environments.
-## Example configuration
+You can also install hk with `brew install hk` or `cargo install hk --locked`. The default Pkl evaluator is built into hk; a separate Pkl installation is optional.
-The generated `hk.pkl` uses hk's built-in linter definitions, which you can extend when a project
-needs different behavior:
+## A configuration you can share
+
+This example uses hk’s built-in whitespace utilities, so it needs no additional linter:
```pkl
amends "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Config.pkl"
import "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Builtins.pkl"
local linters = new Mapping {
- ["eslint"] = Builtins.eslint
- ["prettier"] = (Builtins.prettier) {
- glob = List("*.js", "*.ts", "*.json", "*.md")
- }
+ ["trailing-whitespace"] = Builtins.trailing_whitespace
+ ["newlines"] = Builtins.newlines
}
hooks {
@@ -66,9 +42,7 @@ hooks {
stash = "git"
steps = linters
}
- ["check"] {
- steps = linters
- }
+ ["check"] { steps = linters }
["fix"] {
fix = true
steps = linters
@@ -76,13 +50,32 @@ hooks {
}
```
-Run the same checks directly at any time:
+Add tools such as `Builtins.prettier`, `Builtins.eslint`, or `Builtins.ruff`, or [define your own steps](https://hk.jdx.dev/reference/examples/custom-linters).
-```sh
-hk check # check modified files
-hk fix # fix modified files
-hk check --all # check the entire repository, useful in CI
-```
+## Everyday commands
+
+| Command | Use it to |
+| ------------------------- | ----------------------------------------------------- |
+| `hk check` | Check modified files |
+| `hk fix` | Apply available fixes to modified files |
+| `hk check --all` | Check the repository, including in CI |
+| `hk check --plan` | Preview selected files and steps without running them |
+| `hk check --why prettier` | Explain why a step will run or be skipped |
+
+By convention, checks do not modify files. Fixes may modify and stage files; review `git diff` and `git diff --cached`. Use `hk fix --no-stage` to leave fixes unstaged. The generated pre-commit hook stashes unstaged work before fixing staged files, then restores it afterward. [Learn about hooks and partial commits](https://hk.jdx.dev/hooks).
+
+## Why hk?
+
+- **Coordinate concurrent tools.** File locks protect overlapping steps; diff and file-list checks reduce the work that needs exclusive access.
+- **Reuse linter configurations.** Builtins describe file patterns, check commands, fixes, and tool-specific optimizations.
+- **Keep configuration maintainable.** Pkl provides types, imports, and reusable objects for sharing steps across hooks and projects.
+- **Use your existing toolchain.** Run commands from `PATH`, or use [mise](https://hk.jdx.dev/mise_integration) to manage tools and environments.
+
+Read [how hk works](https://hk.jdx.dev/why-hk), browse [project examples](https://hk.jdx.dev/reference/examples/), or see the [benchmark methodology and results](https://hk.jdx.dev/benchmarks).
+
+## Demo
+
+
## Agent skills
@@ -98,15 +91,9 @@ to those directories, so compatible installers can use the bundled instructions
repository download. Making them available to an agent is
opt-in; see [mise's skills documentation](https://mise.jdx.dev/dev-tools/packslip-resources.html).
-## Documentation
+## Contributing
-- [Getting started](https://hk.jdx.dev/getting_started)
-- [Configuration reference](https://hk.jdx.dev/configuration)
-- [Configuration examples](https://hk.jdx.dev/reference/examples/)
-- [Built-in linters](https://hk.jdx.dev/builtins)
-- [CLI reference](https://hk.jdx.dev/cli/)
-- [Why hk?](https://hk.jdx.dev/why-hk)
-- [Contributing](CONTRIBUTING.md)
+See the [contributing guide](CONTRIBUTING.md) for development setup, tests, and review expectations. hk is released under the [MIT license](LICENSE).
## Sponsors
@@ -129,16 +116,6 @@ opt-in; see [mise's skills documentation](https://mise.jdx.dev/dev-tools/packsli
View all sponsors
-## Demo
-
-
-
-## CI
-
-
-
-
diff --git a/docs/.vitepress/theme/Layout.vue b/docs/.vitepress/theme/Layout.vue
index daddd0691..d9e8f03bd 100644
--- a/docs/.vitepress/theme/Layout.vue
+++ b/docs/.vitepress/theme/Layout.vue
@@ -1,3 +1,11 @@
+
+
@@ -6,128 +14,3 @@
-
-
-
-
diff --git a/docs/.vitepress/theme/banner.css b/docs/.vitepress/theme/banner.css
index 4a9e54fa3..ea0e1d467 100644
--- a/docs/.vitepress/theme/banner.css
+++ b/docs/.vitepress/theme/banner.css
@@ -10,14 +10,14 @@
justify-content: center;
padding: 0.5rem 3.25rem 0.5rem 2.75rem;
background: var(--vp-c-brand-1, #3abff8);
- color: #000;
+ color: var(--vp-button-brand-text, #000);
font-size: 0.9rem;
line-height: 1.4;
text-align: center;
min-height: 2.75rem;
}
.jdx-banner a {
- color: #000;
+ color: var(--vp-button-brand-text, #000);
text-decoration: underline;
font-weight: 600;
}
diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts
index cde1a5f07..010534ac7 100644
--- a/docs/.vitepress/theme/index.ts
+++ b/docs/.vitepress/theme/index.ts
@@ -1,51 +1,59 @@
-import { h, onMounted, onUnmounted } from 'vue'
-import type { Theme } from 'vitepress'
-import DefaultTheme from 'vitepress/theme-without-fonts'
-import Layout from './Layout.vue'
-import HomePage from './HomePage.vue'
-import { initBanner } from './banner'
-import { data as starsData } from '../stars.data'
-import './style.css'
+import { onMounted, onUnmounted } from "vue";
+import type { Theme } from "vitepress";
+import DefaultTheme from "vitepress/theme-without-fonts";
+import Layout from "./Layout.vue";
+import HomePage from "./HomePage.vue";
+import { initBanner } from "./banner";
+import { data as starsData } from "../stars.data";
+import "./style.css";
export default {
extends: DefaultTheme,
Layout,
- enhanceApp({ app, router, siteData }) {
- app.component('HomePage', HomePage)
- initBanner()
+ enhanceApp({ app }) {
+ app.component("HomePage", HomePage);
+ initBanner();
},
setup() {
- let observer: MutationObserver | undefined
+ let observer: MutationObserver | undefined;
onMounted(() => {
const addStarCount = () => {
- if (!starsData.stars) return false
+ if (!starsData.stars) return false;
const githubLinks = document.querySelectorAll(
'.VPSocialLinks a[href*="github.com/jdx/hk"]',
- )
+ );
githubLinks.forEach((githubLink) => {
- if (!githubLink.querySelector('.star-count')) {
- const starBadge = document.createElement('span')
- starBadge.className = 'star-count'
- starBadge.title = 'GitHub Stars'
- const glyph = document.createElement('span')
- glyph.className = 'star-glyph'
- glyph.textContent = '★'
- glyph.setAttribute('aria-hidden', 'true')
- starBadge.append(glyph, starsData.stars)
- githubLink.appendChild(starBadge)
+ if (!githubLink.querySelector(".star-count")) {
+ const starBadge = document.createElement("span");
+ starBadge.className = "star-count";
+ starBadge.title = "GitHub Stars";
+ const glyph = document.createElement("span");
+ glyph.className = "star-glyph";
+ glyph.textContent = "★";
+ glyph.setAttribute("aria-hidden", "true");
+ starBadge.append(glyph, starsData.stars);
+ githubLink.appendChild(starBadge);
}
- })
- return githubLinks.length > 0 && Array.from(githubLinks).every((link) => link.querySelector('.star-count'))
- }
+ });
+ return (
+ githubLinks.length > 0 &&
+ Array.from(githubLinks).every((link) =>
+ link.querySelector(".star-count"),
+ )
+ );
+ };
- if (addStarCount()) return
+ if (addStarCount()) return;
observer = new MutationObserver(() => {
- if (addStarCount()) observer?.disconnect()
- })
- observer.observe(document.querySelector('.VPNav') || document.body, { childList: true, subtree: true })
- })
- onUnmounted(() => observer?.disconnect())
+ if (addStarCount()) observer?.disconnect();
+ });
+ observer.observe(document.querySelector(".VPNav") || document.body, {
+ childList: true,
+ subtree: true,
+ });
+ });
+ onUnmounted(() => observer?.disconnect());
},
-} satisfies Theme
+} satisfies Theme;
diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css
index 78fb4b416..f11595998 100644
--- a/docs/.vitepress/theme/style.css
+++ b/docs/.vitepress/theme/style.css
@@ -1,431 +1,148 @@
-/**
- * Custom HK Theme Styles
- * A unique, modern theme for the hk git hook manager
- */
-
-/* Import VitePress default theme styles */
-@import 'vitepress/dist/client/theme-default/styles/vars.css';
-@import 'vitepress/dist/client/theme-default/styles/base.css';
-@import 'vitepress/dist/client/theme-default/styles/utils.css';
-@import 'vitepress/dist/client/theme-default/styles/components/custom-block.css';
-@import 'vitepress/dist/client/theme-default/styles/components/vp-code.css';
-@import 'vitepress/dist/client/theme-default/styles/components/vp-code-group.css';
-@import 'vitepress/dist/client/theme-default/styles/components/vp-doc.css';
-@import 'vitepress/dist/client/theme-default/styles/components/vp-sponsor.css';
-
-/* Import fonts */
-@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap');
-@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap');
-@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap');
-
-/* Preserve command syntax exactly instead of rendering operators as ligatures. */
-code,
-kbd,
-pre,
-samp {
- font-variant-ligatures: none;
- font-feature-settings: 'liga' 0, 'calt' 0;
-}
-
-/* Custom properties - Dark mode only */
+/* Let VitePress own navigation, search, and responsive layout. */
:root {
- /* Brand colors - Electric Blue */
- --hk-blue-primary: #4CC9F0;
- --hk-blue-secondary: #00B4D8;
- --hk-accent: #3ABFF8;
-
- /* Override VitePress defaults */
- --vp-font-family-base: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
- --vp-font-family-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, 'SF Mono', Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
- --vp-font-family-display: 'Bebas Neue', 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
-
- /* Dark theme colors */
- --vp-c-bg: #0a1628;
- --vp-c-bg-soft: #0f1f35;
- --vp-c-bg-mute: #142840;
-
- --vp-c-brand-1: var(--hk-accent);
- --vp-c-brand-2: var(--hk-blue-primary);
- --vp-c-brand-3: var(--hk-blue-secondary);
- --vp-c-brand-soft: rgba(76, 201, 240, 0.14);
-
- /* Custom accent colors */
- --vp-c-tip-1: var(--hk-blue-primary);
- --vp-c-tip-2: rgba(76, 201, 240, 0.14);
- --vp-c-tip-3: var(--hk-blue-secondary);
-
- /* Code block styling */
- --vp-code-block-bg: #142840;
- --vp-code-line-highlight-color: rgba(76, 201, 240, 0.1);
- --vp-code-copy-code-bg: rgba(76, 201, 240, 0.9);
- --vp-code-copy-code-hover-bg: var(--hk-blue-secondary);
- --vp-code-color: #e4e4e7;
- --vp-code-link-color: var(--hk-blue-primary);
- --vp-code-link-hover-color: var(--hk-accent);
-}
-
-/* Navigation bar - ensure full width and proper background */
-.VPNav {
- width: 100% !important;
- background: var(--vp-c-bg) !important;
- position: relative;
- z-index: 1000 !important;
+ --vp-font-family-base:
+ -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ --vp-font-family-mono:
+ "SFMono-Regular", Consolas, "Liberation Mono", monospace;
+ --vp-c-brand-1: #08768b;
+ --vp-c-brand-2: #086b7e;
+ --vp-c-brand-3: #086476;
+ --vp-c-brand-soft: rgb(8 118 139 / 10%);
+ --vp-c-bg: #fcfcfa;
+ --vp-c-bg-alt: #f3f5f4;
+ --vp-c-bg-soft: #f2f5f5;
+ --vp-c-bg-elv: #fff;
+ --vp-c-text-1: #192e38;
+ --vp-c-text-2: #52636b;
+ --vp-c-text-3: #65757d;
+ --vp-c-divider: #dce3e4;
+ --vp-button-brand-text: #fff;
+ --vp-button-brand-hover-text: #fff;
+ --vp-code-block-bg: #f2f5f5;
+ --hk-code-keyword: #96500f;
+ --hk-write-bg: rgb(150 80 15 / 10%);
+}
+.dark {
+ --vp-c-brand-1: #73d5df;
+ --vp-c-brand-2: #8de1e8;
+ --vp-c-brand-3: #73d5df;
+ --vp-c-brand-soft: rgb(115 213 223 / 10%);
+ --vp-c-bg: #101a23;
+ --vp-c-bg-alt: #0c151d;
+ --vp-c-bg-soft: #16232e;
+ --vp-c-bg-elv: #1b2a36;
+ --vp-c-text-1: #e8edf0;
+ --vp-c-text-2: #adbdc9;
+ --vp-c-text-3: #8a9eac;
+ --vp-c-divider: #2a3a46;
+ --vp-button-brand-text: #10242b;
+ --vp-button-brand-hover-text: #10242b;
+ --vp-code-block-bg: #14212b;
+ --hk-code-keyword: #eac18e;
+ --hk-write-bg: rgb(234 193 142 / 12%);
}
-
-.VPNavBar {
- width: 100% !important;
- background: var(--vp-c-bg) !important;
- position: relative;
- z-index: 1000 !important;
-}
-
-/* Ensure nav screen (mobile menu) also has proper background */
-.VPNavScreen {
- background: var(--vp-c-bg) !important;
- z-index: 1000 !important;
-}
-
-/* Sidebar styling */
-.VPSidebar {
- background: var(--vp-c-bg) !important;
-}
-
-.VPSidebarNav {
- background: var(--vp-c-bg) !important;
-}
-
-/* Search box styling */
-.VPNavBarSearch .DocSearch-Button {
- background: var(--vp-c-bg-mute) !important;
-}
-
-.VPNavBarSearch .DocSearch-Button:hover {
- background: var(--vp-c-bg-soft) !important;
-}
-
-/* Custom nav brand styling */
-.VPNavBarTitle .logo {
- height: 36px !important;
- width: auto !important;
-}
-
.VPNavBarTitle .title {
- font-family: 'Bebas Neue', sans-serif !important;
- font-weight: 400;
- font-size: 1.8rem;
- letter-spacing: 2px;
- text-transform: uppercase;
- background: linear-gradient(135deg, var(--hk-blue-primary), var(--hk-blue-secondary));
- -webkit-background-clip: text;
- background-clip: text;
- -webkit-text-fill-color: transparent;
- transition: all 0.4s ease;
- background-size: 200% 200%;
- background-position: 0% 50%;
+ font-size: 24px;
+ font-weight: 750;
+ letter-spacing: -0.06em;
}
-
-.VPNavBarTitle:hover .title {
- background: linear-gradient(135deg, #3ABFF8, #00B4D8);
- -webkit-background-clip: text;
- background-clip: text;
- -webkit-text-fill-color: transparent;
- transform: none;
+.VPNavBarTitle .logo {
+ height: 30px;
}
-
-/* Enhanced buttons */
-.VPButton {
- position: relative;
- overflow: hidden;
- transition: all 0.3s ease;
- font-family: var(--vp-font-family-display);
- font-weight: 400;
- font-size: 1.1rem;
- letter-spacing: 1px;
- text-transform: uppercase;
+.vp-doc h1 {
+ font-size: 36px;
+ line-height: 1.2;
+ letter-spacing: -0.035em;
}
-
-/* Only apply gradient to brand buttons on specific pages, not globally */
-.home-page .VPButton.brand {
- background: linear-gradient(135deg, var(--hk-blue-primary), var(--hk-blue-secondary));
- border: none;
- box-shadow: 0 4px 15px rgba(76, 201, 240, 0.3);
+.vp-doc h2 {
+ letter-spacing: -0.025em;
}
-
-.home-page .VPButton.brand:hover {
- transform: translateY(-2px);
- box-shadow: 0 6px 20px rgba(76, 201, 240, 0.4);
+.vp-doc h3 {
+ letter-spacing: -0.015em;
}
-
-/* Code blocks with custom styling */
-.vp-code-group .tabs {
- background: linear-gradient(90deg, var(--hk-blue-primary), var(--hk-blue-secondary));
- padding: 2px;
- border-radius: 8px 8px 0 0;
+.vp-doc p,
+.vp-doc li {
+ line-height: 1.8;
}
-
-.vp-code-group .tabs label {
- background: var(--vp-c-bg);
+.vp-doc > div > h1 + p {
+ font-size: 18px;
color: var(--vp-c-text-2);
- margin: 2px;
- border-radius: 6px 6px 0 0;
- transition: all 0.3s ease;
-}
-
-.vp-code-group .tabs label:hover {
- color: var(--hk-blue-primary);
-}
-
-.vp-code-group input:checked + label {
- background: var(--vp-code-block-bg);
- color: var(--hk-accent);
-}
-
-/* Sidebar enhancements */
-/* Removed sidebar gradient to fix dark mode appearance */
-
-.VPSidebarItem.is-active > .item > .link > .text {
- color: var(--hk-blue-primary);
- font-weight: 600;
- position: relative;
-}
-
-.VPSidebarItem.is-active > .item > .link::before {
- content: '';
- position: absolute;
- left: -12px;
- top: 50%;
- transform: translateY(-50%);
- width: 3px;
- height: 20px;
- background: var(--hk-blue-primary);
- border-radius: 2px;
-}
-
-/* Removed pulse animation */
-
-/* Custom tip/warning/danger blocks */
-.custom-block.tip {
- border-color: var(--hk-blue-primary);
- background: linear-gradient(135deg, rgba(76, 201, 240, 0.05), rgba(0, 180, 216, 0.05));
-}
-
-.custom-block.tip .custom-block-title {
- color: var(--hk-blue-primary);
- font-weight: 700;
-}
-
-/* Enhanced tables */
-table {
- border-collapse: separate;
- border-spacing: 0;
- border-radius: 8px;
- overflow: hidden;
- box-shadow: 0 2px 8px rgba(76, 201, 240, 0.1);
-}
-
-thead tr {
- background: linear-gradient(90deg, var(--hk-blue-primary), var(--hk-blue-secondary));
-}
-
-thead th {
- color: white !important;
- font-weight: 600;
- padding: 12px 16px;
-}
-
-tbody tr:hover {
- background: rgba(76, 201, 240, 0.05);
- transition: background 0.2s ease;
-}
-
-/* Code blocks - dark mode styling */
-.vp-doc div[class*='language-'] {
- background-color: #142840;
}
-
-.vp-doc div[class*='language-'] code {
- color: #e4e4e7;
- font-weight: 500;
-}
-
-/* Terminal-style code blocks */
-pre.language-bash,
-pre.language-shell,
-pre.language-sh {
- position: relative;
- padding-top: 36px;
-}
-
-pre.language-bash::before,
-pre.language-shell::before,
-pre.language-sh::before {
- content: 'Terminal';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- padding: 8px 16px;
- background: linear-gradient(90deg, var(--hk-blue-primary), var(--hk-blue-secondary));
- color: white;
- font-size: 12px;
- font-weight: 600;
- font-family: var(--vp-font-family-base);
-}
-
-/* Feature cards - removed animated gradient to prevent issues */
-.VPFeature {
- position: relative;
- background: var(--vp-c-bg-soft);
- padding: 24px;
- border-radius: 12px;
- border: 1px solid var(--vp-c-divider);
- transition: all 0.3s ease;
-}
-
-.VPFeature:hover {
- border-color: var(--hk-blue-primary);
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(76, 201, 240, 0.15);
-}
-
-/* Removed gradient-rotate animation */
-
-/* Search modal styling */
-.VPLocalSearchBox {
- --vp-local-search-highlight-bg: rgba(76, 201, 240, 0.2);
- --vp-local-search-highlight-text: var(--hk-blue-primary);
- z-index: 1100 !important;
-}
-
-/* Custom scrollbar */
-::-webkit-scrollbar {
- width: 10px;
- height: 10px;
+.vp-doc a {
+ text-underline-offset: 3px;
}
-
-::-webkit-scrollbar-track {
+.vp-doc th {
background: var(--vp-c-bg-soft);
+ font-size: 14px;
}
-
-::-webkit-scrollbar-thumb {
- background: linear-gradient(180deg, var(--hk-blue-primary), var(--hk-blue-secondary));
- border-radius: 5px;
-}
-
-::-webkit-scrollbar-thumb:hover {
- background: var(--hk-blue-secondary);
-}
-
-/* Smooth transitions - only apply to interactive elements, not globally */
-
-/* Custom link styling */
-a {
- color: var(--hk-blue-primary);
- text-decoration: none;
- position: relative;
- transition: color 0.3s ease;
-}
-
-a:hover {
- color: var(--hk-blue-secondary);
-}
-
-.vp-doc a {
- font-weight: 500;
+.vp-doc td {
+ font-size: 14px;
}
-
-.vp-doc a::after {
- content: '';
- position: absolute;
- left: 0;
- bottom: -2px;
- width: 0;
- height: 2px;
- background: linear-gradient(90deg, var(--hk-blue-primary), var(--hk-blue-secondary));
- transition: width 0.3s ease;
+.vp-doc div[class*="language-"] {
+ border: 1px solid var(--vp-c-divider);
}
-
-.vp-doc a:hover::after {
- width: 100%;
+.vp-doc .custom-block {
+ border-radius: 6px;
}
-
-/* Badge styling - removed gradient to prevent issues */
-
-/* Use Bebas Neue for main headings */
-h1, .vp-doc h1 {
- font-family: var(--vp-font-family-display);
- font-weight: 400;
- letter-spacing: 2px;
+.VPSidebarItem.level-0 > .item > .text {
+ font-size: 12px;
+ letter-spacing: 0.04em;
text-transform: uppercase;
}
-
-/* Large section titles */
-.home-page .section-title {
- font-family: 'Bebas Neue', sans-serif !important;
- font-weight: 400 !important;
- letter-spacing: 2px !important;
- text-transform: uppercase !important;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- text-rendering: optimizeLegibility;
+.VPLocalSearchBox {
+ --vp-local-search-highlight-bg: var(--vp-c-brand-soft);
+ --vp-local-search-highlight-text: var(--vp-c-brand-1);
}
-
-/* Hero section titles */
-.hero-title, .title-main {
- font-family: var(--vp-font-family-display);
- font-weight: 400;
- letter-spacing: 4px;
- text-transform: uppercase;
+:focus-visible {
+ outline: 2px solid var(--vp-c-brand-1);
+ outline-offset: 4px;
}
-
-/* Social links */
-.VPSocialLinks {
- align-items: center !important;
+::selection {
+ background: var(--vp-c-brand-soft);
}
-
.VPSocialLinks a[href*="github.com/jdx/hk"] {
- display: inline-flex !important;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 0;
position: relative;
- padding-bottom: 12px !important;
- margin-bottom: -12px !important;
}
-
-.VPSocialLinks a[href*="github.com/jdx/hk"] svg {
- display: block !important;
- width: 20px;
- height: 20px;
- margin-top: 2px;
-}
-
.VPSocialLinks .star-count {
position: absolute;
- bottom: 0;
+ top: 31px;
left: 50%;
transform: translateX(-50%);
- font-size: 0.6rem;
- font-weight: 600;
- color: var(--vp-c-text-3);
- font-family: var(--vp-font-family-mono);
+ font: 9px/1 var(--vp-font-family-mono);
white-space: nowrap;
- line-height: 1;
+ color: var(--vp-c-text-2);
}
-
-.VPSocialLinks .star-count .star-glyph {
- font-family: -apple-system, "Segoe UI Symbol", sans-serif;
- line-height: 1;
+.VPSocialLinks .star-glyph {
margin-right: 0.2em;
}
-
-.VPSocialLinks a[href*="github.com/jdx/hk"]:hover .star-count {
- color: var(--vp-c-brand-1);
-}
-
@media (max-width: 640px) {
+ .vp-doc h1 {
+ font-size: 30px;
+ }
.VPSocialLinks .star-count {
display: none;
}
}
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ scroll-behavior: auto !important;
+ animation: none !important;
+ transition: none !important;
+ }
+}
+
+/* Preserve CLI punctuation and keep search above the announcement banner. */
+code,
+kbd,
+pre,
+samp {
+ font-variant-ligatures: none;
+ font-feature-settings:
+ "liga" 0,
+ "calt" 0;
+}
+.VPLocalSearchBox {
+ z-index: 1100 !important;
+}
diff --git a/docs/about.md b/docs/about.md
index 9bb699d1c..e8e998523 100644
--- a/docs/about.md
+++ b/docs/about.md
@@ -1,29 +1,27 @@
-# About
+---
+description: Why hk exists, how it fits into a toolchain, and where to contribute.
+---
-hk is built by [@jdx](https://github.com/jdx).
+# About hk
-## Why does this exist?
+hk is a Git hook manager and project linting tool built by [@jdx](https://github.com/jdx). It is written in Rust and released under the [MIT license](https://github.com/jdx/hk/blob/main/LICENSE).
-git hooks need to be fast above all else, or developers won't use them. Parallelism
-is the best (and likely only) way to achieve acceptable performance at the git hook manager level.
+## Why it exists
-Existing alternatives to hk such as [lefthook](https://github.com/evilmartians/lefthook) support
-very basic parallel execution of shell scripts—however, because linters may edit files, this naive approach
-can break down if multiple linters affect the same file.
+Git hooks sit directly in the path of a commit. Their speed matters, but running formatters concurrently introduces a coordination problem: two tools can read the same file and then overwrite one another’s changes.
-I felt that a git hook manager that had tighter integration with the linters would be able to leverage
-read/write file locks to enable more aggressive parallelism while preventing race conditions. This read/write locking is the primary reason
-I built hk. There are other design decisions worth noting, though, that I think make hk a better experience than its peers:
+hk uses read/write file locks to coordinate those tools. Checks can share read access; fixes take exclusive access to the files they modify. Builtins expose tool features such as diff output and lists of files needing changes, which help hk keep more work running concurrently.
-- hk has a large set of [builtins](https://github.com/jdx/hk/tree/main/pkl/builtins) you can use for common linters like `prettier` or `black`.
-- hk can stash unstaged changes before running "fix" hooks (see the `stash` setting). This prevents a common issue with pre-commit hooks where files containing both staged and
- unstaged changes get modified and the unstaged changes end up being staged erroneously.
-- By default, hk uses libgit2 to directly interact with git instead of shelling out many times to `git`.
- This generally makes hk much faster, but there are situations, such as repositories using `fsmonitor`, where it may perform worse.
-- hk is a Rust CLI which gives it great startup performance.
-- hk is designed to work well with my other project [mise-en-place](https://mise.jdx.dev) which makes it easy to manage dependencies for hk linters.
+[Why hk?](/why-hk) explains the execution model and its tradeoffs.
-## Contributing
+## Where it fits
-Contributions are welcome! Please open an issue or submit a PR. I encourage reaching out to me before submitting a feature PR to make sure it's something I will be interested in
-maintaining.
+hk decides which checks to run, on which files, and when. Linters still own their rules and configuration. Your package manager provides their executables; [mise](/mise_integration) can manage those versions and the environment used by Git.
+
+Configuration uses [Pkl](/pkl_introduction) for types, imports, and reusable step definitions. The default evaluator is included in hk.
+
+## Get involved
+
+Report bugs in [GitHub issues](https://github.com/jdx/hk/issues), discuss ideas in [GitHub Discussions](https://github.com/jdx/hk/discussions) or [Discord](https://discord.gg/UBa7pJUN7Z), and read the [contributing guide](/contributing) before starting a larger change.
+
+For something less technical, there is also a [sea shanty](/shanty).
diff --git a/docs/benchmarks.md b/docs/benchmarks.md
index 1f6979c28..2f1c3314d 100644
--- a/docs/benchmarks.md
+++ b/docs/benchmarks.md
@@ -1,118 +1,79 @@
-
# Benchmarks
-These benchmarks compare hk, lefthook, pre-commit, and prek running 10 linters on a synthetic project. Since lefthook's `parallel: true` mode has race conditions when linters touch overlapping files, we run lefthook in safe (sequential) mode, the only correct option. pre-commit and prek both run hooks sequentially.
-
-hk is the only tool that runs linters in parallel **and** safely.
+These results measure one synthetic workload with overlapping linter file patterns. They illustrate how orchestration affects this setup; they are not a prediction for every repository or a survey of every tool’s available configuration.
-See [Why hk?](/why-hk) for context on why these differences exist.
+The recorded run was generated on **{{ data.generated.split('T')[0] }}**. No new benchmark run is implied by this page’s last-updated date.
-## Setup
+## Recorded results
-A synthetic project with **~6,000 files** across multiple languages:
+Mean wall time, in seconds. Lower is faster.
-- 4000 Python, 500 JavaScript/TypeScript, 500 JSON, 500 Shell, 250 YAML, 200 CSS, 200 Markdown
+| Tool | All files ({{ data.total_files }}) | Staged changes ({{ data.staged_files }}) |
+| ---------- | ------------------------------------------------------ | ----------------------------------------------------------- |
+| hk | {{ fmt(data.scenarios.all_files.hk.mean) }} | {{ fmt(data.scenarios.staged_changes.hk.mean) }} |
+| lefthook | {{ fmt(data.scenarios.all_files.lefthook.mean) }} | {{ fmt(data.scenarios.staged_changes.lefthook.mean) }} |
+| pre-commit | {{ fmt(data.scenarios.all_files['pre-commit'].mean) }} | {{ fmt(data.scenarios.staged_changes['pre-commit'].mean) }} |
+| prek | {{ fmt(data.scenarios.all_files.prek.mean) }} | {{ fmt(data.scenarios.staged_changes.prek.mean) }} |
-Ten linters with overlapping file coverage:
+
-| Linter | Files | How hk avoids write locks |
-|--------|-------|---------------------------|
-| prettier | `*.{js,ts,css,md}` | `check_list_files` — only locks files that need fixing |
-| eslint | `*.{js,ts}` | Falls back to write lock (eslint has no diff/list mode) |
-| black | `*.py` | `check_diff` — hk applies the diff itself |
-| ruff check | `*.py` | Check only — read lock |
-| ruff format | `*.py` | `check_diff` — hk applies the diff itself |
-| jq | `*.json` | `check_diff` |
-| yq | `*.{yml,yaml}` | `check_diff` |
-| shfmt | `*.{sh,bash}` | `check_diff` |
-| trailing-whitespace | `*` (all files) | `check_diff` via `hk util` (built-in Rust) |
-| newlines | `*` (all files) | `check_diff` via `hk util` (built-in Rust) |
+[Download the recorded data](/benchmark-data.json), including standard deviations, minimums, and maximums.
-The `trailing-whitespace` and `newlines` linters use `**/*` globs, so they overlap with **every other linter**. pre-commit and prek always run hooks sequentially. lefthook supports `parallel: true` but has no file-level coordination, so overlapping linters cause race conditions, and we run it in safe (sequential) mode. hk is the only tool that runs everything in parallel safely, using file-level read/write locks.
+## Workload
-## Results
+The generator defaults to roughly 6,000 files: 4,000 Python, 500 JavaScript/TypeScript, 500 JSON, 500 shell, 250 YAML, 200 CSS, and 200 Markdown files, plus project configuration.
-
+Ten configured steps include ESLint, Prettier, Black, Ruff linting, Ruff formatting, jq, yq, shfmt, trailing whitespace, and final newlines. The whitespace steps overlap with the language-specific steps.
-
+The committed runner:
-### All Files ({{ data.total_files || '~6,000' }} files, 10 linters)
+- Invokes hk’s pre-commit hook in fix mode, with stashing disabled through `HK_STASH=false`.
+- Configures lefthook with sequential execution to avoid concurrent writes from overlapping formatters in this workload.
+- Runs pre-commit and prek using the provided hook definitions.
+- Resets the fixture between runs, primes hk’s configuration cache, and uses Hyperfine warmups and repeated measurements.
-| Tool | Time | |
-|------|------|-|
-| **hk** | **{{ fmt(data.scenarios.all_files?.hk?.mean) }}** | |
-| lefthook | {{ fmt(data.scenarios.all_files?.lefthook?.mean) }} | {{ speedup('all_files', 'lefthook') }} |
-| pre-commit | {{ fmt(data.scenarios.all_files?.['pre-commit']?.mean) }} | {{ speedup('all_files', 'pre-commit') }} |
-| prek | {{ fmt(data.scenarios.all_files?.prek?.mean) }} | {{ speedup('all_files', 'prek') }} |
+The [runner](https://github.com/jdx/hk/blob/main/benchmark/run.sh) and [tool configurations](https://github.com/jdx/hk/tree/main/benchmark/parallel) define the comparison. These choices matter as much as the timing values.
-### Staged Changes ({{ data.staged_files || '~50' }} files)
+## Limitations
-| Tool | Time | |
-|------|------|-|
-| **hk** | **{{ fmt(data.scenarios.staged_changes?.hk?.mean) }}** | |
-| lefthook | {{ fmt(data.scenarios.staged_changes?.lefthook?.mean) }} | {{ speedup('staged_changes', 'lefthook') }} |
-| pre-commit | {{ fmt(data.scenarios.staged_changes?.['pre-commit']?.mean) }} | {{ speedup('staged_changes', 'pre-commit') }} |
-| prek | {{ fmt(data.scenarios.staged_changes?.prek?.mean) }} | {{ speedup('staged_changes', 'prek') }} |
+This workload favors concurrent work across languages while also exercising overlapping formatters. A small project, a single linter, or tools that already parallelize internally may see different results.
-
Last generated: {{ data.generated?.split('T')[0] }}
+Stashing is disabled in the runner, so the results do not measure partial-commit restoration. Hyperfine is configured to tolerate nonzero exits from lint commands; timings alone do not establish equivalent fixes or successful checks.
-
+The recorded JSON does not include machine specifications or exact tool versions. Treat it as a historical example and rerun the workload with those details recorded before using the numbers for a tool-selection decision. Current scripts may also differ from the ones used for the recorded result.
-## Reproducing
+## Reproduce
-Everything is in the `benchmark/` directory.
+Use a disposable directory. The benchmark runner resets its fixture repository and overwrites generated results.
-### Prerequisites
+Install `hk`, `hyperfine`, `lefthook`, `pre-commit`, `prek`, `prettier`, `eslint`, `black`, `ruff`, `shfmt`, `jq`, `yq`, and `uv`, and record their versions. The shell scripts expect a Unix-like environment and compatible command-line utilities.
-```bash
-mise use hyperfine lefthook prettier eslint shfmt jq yq
-uv tool install pre-commit prek black ruff
-```
-
-### Generate and run
+From the repository root:
-```bash
-# Generate a synthetic project (~6,000 files by default)
+```sh
benchmark/generate-project.sh /tmp/hk-bench
-
-# Run benchmarks
benchmark/run.sh /tmp/hk-bench
+```
+
+To change the workload or number of repetitions:
-# Customize
+```sh
NUM_JS=500 NUM_PY=500 benchmark/generate-project.sh /tmp/hk-bench
RUNS=20 WARMUP=3 benchmark/run.sh /tmp/hk-bench
```
-Results are saved as JSON in `benchmark/results/` and both a chart and data file are generated in `docs/public/`.
+Results are written to `benchmark/results/`; the runner also updates `docs/public/benchmark.png` and `docs/public/benchmark-data.json`.
+
+For your own project, start with [hk timing reports](/logging#a-run-is-slow). See [Why hk?](/why-hk) for the execution model.
diff --git a/docs/builtins.md b/docs/builtins.md
index 746c6948b..aa322ce09 100644
--- a/docs/builtins.md
+++ b/docs/builtins.md
@@ -1,21 +1,22 @@
---
-outline: "deep"
+outline: [2, 3]
+description: Browse reusable linter and formatter definitions, customize them, and understand tool requirements.
---
-# Built-in Linters Reference
+# Built-in linters
-hk provides 150+ pre-configured linters and formatters through the `Builtins` module. Each builtin supplies the command, file matching, batching, and other hk behavior; the corresponding tool must be available in the step's environment.
+Builtins are reusable Pkl step definitions for linters, formatters, and hk’s own utilities. They supply file patterns, check and fix commands, and optimizations such as diff output.
-## Usage
+**Install the tools separately.** A builtin invokes executables from your environment; it does not install them. Use your project’s package manager or [mise](/mise_integration).
-Import and use builtins in your `hk.pkl`:
+## Use a builtin
```pkl
amends "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Config.pkl"
import "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Builtins.pkl"
hooks {
- ["pre-commit"] {
+ ["check"] {
steps {
["prettier"] = Builtins.prettier
["eslint"] = Builtins.eslint
@@ -24,16 +25,42 @@ hooks {
}
```
-You can also customize builtins:
+`Builtins.prettier` is the Pkl property name. The step name, `"prettier"`, is your label for selecting the step in commands such as `hk check --step prettier`.
+
+Keep the schema and Builtins imports on the same version. The catalogue below describes the version of the source used to build this website; an older pinned package may differ.
+
+## Customize a builtin
+
+Amend a builtin to keep its defaults while changing specific properties:
```pkl
["prettier"] = (Builtins.prettier) {
- batch = false // Override the default batch setting
- glob = List("*.js", "*.ts") // Override file patterns
+ glob = List("*.js", "*.ts", "*.json")
+ exclude = List("**/generated/**")
+ batch = false
+}
+```
+
+A property assignment replaces that property. If you override `glob` or `exclude`, include every pattern you want to retain.
+
+Use dependencies for ordering and profiles for optional checks:
+
+```pkl
+["prettier"] = (Builtins.prettier) {
+ depends = "eslint"
+}
+["mypy"] = (Builtins.mypy) {
+ profiles = List("types")
}
```
-### Tool availability
+Run the type checker with `hk check --profile types`. See [configuration](/configuration) for groups, workspaces, and command templates.
+
+## Utilities included with hk
+
+Steps such as `Builtins.trailing_whitespace`, `Builtins.newlines`, and `Builtins.check_merge_conflict` invoke [`hk util`](/cli/util) commands. These need no separate linter executable.
+
+## Tool availability
Builtins configure how hk invokes a tool; they do not install that tool. The
executable used by the builtin must be on `PATH`, or the step must use a `prefix`
@@ -53,77 +80,14 @@ locally by aube, prefix its builtin with `aube exec`:
Use an argv list for builtins backed by structured commands. A string prefix such
as `"aube exec"` or `"mise x --"` cannot be combined with those commands.
-The generated list below summarizes each builtin. The complete command and defaults
-for each builtin are defined in the corresponding Pkl file in
-[`pkl/builtins`](https://github.com/jdx/hk/tree/main/pkl/builtins).
+## Available builtins
-## Available Builtins
+The following catalogue is generated from the builtin definitions. Each entry includes the exact Pkl property to use. Refer to the [source definitions](https://github.com/jdx/hk/tree/main/pkl/builtins) for additional options and tests.
-## Customizing Builtins
-
-### Override Properties
-
-```pkl
-["prettier"] = (Builtins.prettier) {
- // Override glob patterns
- glob = List("src/**/*.js", "src/**/*.ts")
-
- // Disable batch processing
- batch = false
-
- // Add environment variables
- env {
- ["PRETTIER_CONFIG"] = ".prettierrc.json"
- }
-}
-```
-
-### Add Dependencies
-
-```pkl
-["eslint"] = (Builtins.eslint) {
- // Run after prettier
- depends = "prettier"
-}
-```
-
-### Workspace-Specific Configuration
-
-```pkl
-["cargo_clippy"] = (Builtins.cargo_clippy) {
- // Only run in directories with Cargo.toml
- workspace_indicator = "Cargo.toml"
-
- // Custom command using workspace
- check = "cargo clippy --manifest-path {{workspace}}/Cargo.toml"
-}
-```
-
-### Profile-Based Configuration
-
-```pkl
-["mypy"] = (Builtins.mypy) {
- // Only run with "python" profile
- profiles = List("python")
-}
-```
-
-## Creating Custom Steps
-
-If a builtin doesn't exist for your tool:
-
-```pkl
-["custom-tool"] {
- glob = List("*.custom")
- check = "custom-tool --check {{files}}"
- fix = "custom-tool --fix {{files}}"
- batch = true // Enable parallel processing
-}
-```
+## Add a tool of your own
-## See Also
+If there is no builtin, define a step with `glob`, `check`, and an optional `fix` command. Only enable batching if the tool can process independent subsets of files correctly.
-- [Configuration Guide](/configuration)
-- [Getting Started](/getting_started)
+See [custom steps](/reference/examples/custom-linters) for a complete example, or [contributing](/contributing#add-a-builtin) to contribute a reusable definition.
diff --git a/docs/ci.md b/docs/ci.md
new file mode 100644
index 000000000..63f9c5aca
--- /dev/null
+++ b/docs/ci.md
@@ -0,0 +1,88 @@
+---
+description: Run hk checks in CI, select changed files, enable profiles, and keep diagnostics useful.
+---
+
+# Continuous integration
+
+Use `hk check --all` to run the project’s checks against a checkout. CI must install hk and every tool used by the configured steps, just as a developer’s machine does.
+
+## Share local and CI checks
+
+Define a `check` hook that reuses your linter mapping:
+
+```pkl
+hooks {
+ ["check"] { steps = linters }
+}
+```
+
+Then run:
+
+```sh
+hk validate
+hk check --all
+```
+
+No `hk install` step is needed to invoke hk directly in CI. Keep check commands read-only so failures report what needs to change. Use `hk fix` locally, review the changes, and commit them.
+
+## Set up the environment
+
+With a committed `mise.toml`, the essential CI commands are:
+
+```sh
+mise install
+mise exec -- hk check --all
+```
+
+If your linters are project dependencies, also run the package manager’s install command and expose its executable directory to hk. The [mise integration guide](/mise_integration) shows a Node.js example.
+
+Pin tool versions in the project so local and CI runs use the same rules. Keep hk’s Pkl package imports versioned as well.
+
+## Check a branch’s changes
+
+A full check is the simplest baseline. For large repositories, select files that differ between two references:
+
+```sh
+hk check --from-ref origin/main --to-ref HEAD
+```
+
+Replace `origin/main` with your target branch and ensure the checkout contains both references and enough history to compare them. Shallow clones may need an additional fetch.
+
+Locally, `hk check --pr` selects changes against the detected default branch. For CI, explicit references make the comparison easier to inspect.
+
+::: tip Changed files are a filter
+Reference selection chooses file paths; the commands run against the current checkout. It does not check out historical versions. A changed-file check also cannot determine every downstream effect of a shared configuration or dependency change.
+:::
+
+## Enable additional checks
+
+Use profiles for checks that are too expensive for every commit:
+
+```pkl
+["typecheck"] = (Builtins.tsc) {
+ profiles = List("slow")
+}
+```
+
+Enable them explicitly:
+
+```sh
+hk check --all --slow
+hk check --all --profile ci --profile slow
+```
+
+A step with multiple positive profiles requires all of them. A profile named `ci` is a label you enable; do not rely on its name to activate it automatically.
+
+## Collect useful diagnostics
+
+```sh
+hk check --all --no-fail-fast
+hk check --all --plan --json
+HK_TIMING_JSON=hk-timing.json hk check --all
+```
+
+`--no-fail-fast` collects failures from remaining steps. A plan shows selected steps without executing them. The timing file records total and per-step wall time; parallel step durations should not be added together as a total.
+
+Use `hk check --all --format jsonl` for structured execution events or `--sarif hk.sarif` for normalized diagnostics. See [coding agents](/agents) for command effects and exact file lists.
+
+See [troubleshooting](/logging) for log levels, traces, and configuration inspection.
diff --git a/docs/cli/agent.md b/docs/cli/agent.md
index 6ad6b0a7a..999b45885 100644
--- a/docs/cli/agent.md
+++ b/docs/cli/agent.md
@@ -1,3 +1,8 @@
+---
+title: "hk agent"
+description: "Generate integration snippets for review before adding them to an agent host."
+---
+
# `hk agent`
@@ -16,3 +21,19 @@ Generate integration snippets for coding agents
- [`hk agent hooks <--target >`](/cli/agent/hooks.md)
- [`hk agent instructions <--target >`](/cli/agent/instructions.md)
- [`hk agent mcp <--target >`](/cli/agent/mcp.md)
+
+
+
+## Examples
+
+Generate integration snippets for review before adding them to an agent host.
+
+```sh
+hk agent instructions --target codex
+hk agent hooks --target claude-code
+hk agent mcp --target vscode
+```
+
+## Learn more
+
+[Coding agents](/agents) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/agent/hooks.md b/docs/cli/agent/hooks.md
index 93d1da55d..ea4b3beec 100644
--- a/docs/cli/agent/hooks.md
+++ b/docs/cli/agent/hooks.md
@@ -1,3 +1,8 @@
+---
+title: "hk agent hooks"
+description: "Print a hook or task snippet to review and merge into your host configuration."
+---
+
# `hk agent hooks`
@@ -14,3 +19,18 @@ Print a hook configuration for an agent or editor
**Choices:** `codex`, `claude-code`, `vscode`
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Print a hook or task snippet to review and merge into your host configuration.
+
+```sh
+hk agent hooks --target claude-code
+hk agent hooks --target vscode
+```
+
+## Learn more
+
+[Coding agents](/agents) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/agent/instructions.md b/docs/cli/agent/instructions.md
index 240ad23d2..6d06609c3 100644
--- a/docs/cli/agent/instructions.md
+++ b/docs/cli/agent/instructions.md
@@ -1,3 +1,8 @@
+---
+title: "hk agent instructions"
+description: "Print project instructions without editing an agent configuration."
+---
+
# `hk agent instructions`
@@ -14,3 +19,18 @@ Print project instructions for a coding agent
**Choices:** `codex`, `claude-code`, `generic`
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Print project instructions without editing an agent configuration.
+
+```sh
+hk agent instructions --target codex
+hk agent instructions --target generic
+```
+
+## Learn more
+
+[Coding agents](/agents) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/agent/mcp.md b/docs/cli/agent/mcp.md
index 24f4e94fb..3a051b4dc 100644
--- a/docs/cli/agent/mcp.md
+++ b/docs/cli/agent/mcp.md
@@ -1,3 +1,8 @@
+---
+title: "hk agent mcp"
+description: "Print an MCP configuration for your host, then review its project path."
+---
+
# `hk agent mcp`
@@ -14,3 +19,18 @@ Print an MCP server configuration
**Choices:** `codex`, `claude-desktop`, `claude-code`, `vscode`
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Print an MCP configuration for your host, then review its project path.
+
+```sh
+hk agent mcp --target codex
+hk agent mcp --target claude-desktop
+```
+
+## Learn more
+
+[Coding agents](/agents) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/builtins.md b/docs/cli/builtins.md
index ccb77a3db..ced6da5e8 100644
--- a/docs/cli/builtins.md
+++ b/docs/cli/builtins.md
@@ -1,3 +1,8 @@
+---
+title: "hk builtins"
+description: "List builtin names, then use them in hk.pkl. External tools must be installed separately."
+---
+
# `hk builtins`
@@ -10,3 +15,17 @@ List all available builtin linters
## Flags
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+List builtin names, then use them in hk.pkl. External tools must be installed separately.
+
+```sh
+hk builtins
+```
+
+## Learn more
+
+[Getting started](/getting_started) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/cache/clear.md b/docs/cli/cache/clear.md
index d20a5dba5..51e989384 100644
--- a/docs/cli/cache/clear.md
+++ b/docs/cli/cache/clear.md
@@ -1,3 +1,8 @@
+---
+title: "hk cache clear"
+description: "Clear hk's cache when diagnosing stale evaluated configuration."
+---
+
# `hk cache clear`
@@ -10,3 +15,18 @@ Clear the cache directory
## Flags
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Clear hk's cache when diagnosing stale evaluated configuration.
+
+```sh
+hk cache clear
+hk validate
+```
+
+## Learn more
+
+[Getting started](/getting_started) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/check.md b/docs/cli/check.md
index a3ff30672..27f0b6b56 100644
--- a/docs/cli/check.md
+++ b/docs/cli/check.md
@@ -1,3 +1,8 @@
+---
+title: "hk check"
+description: "Check a full repository or inspect one step before running it."
+---
+
# `hk check`
@@ -15,10 +20,10 @@ Runs each step's check command, which by convention only reports problems. If th
## Flags
-- **`-a --all`** — Run on all files instead of just staged files
+- **`-a --all`** — Select all tracked and eligible untracked files, then apply step filters
- **`-c --check`** — Run check command instead of fix command
- **`-e --exclude `** — Exclude files that otherwise would have been selected
-- **`-f --fix`** — Run fix command instead of check command (this is the default behavior unless HK_FIX=0)
+- **`-f --fix`** — Run fix commands instead of check commands for this invocation
- **`-g --glob `** — Run on files that match these glob patterns
- **`-J --json`** — Output the plan as JSON when combined with --plan or --why
- **`-P --plan`** — Print the plan instead of running the hook
@@ -30,7 +35,7 @@ Runs each step's check command, which by convention only reports problems. If th
**Choices:** `human`, `json`, `jsonl`
-- **`--from-ref `** — Start reference for checking files (requires --to-ref)
+- **`--from-ref `** — Select files changed since this reference; optionally pair with --to-ref
- **`--no-fail-fast`** — Continue on failures (opposite of --fail-fast)
- **`--no-stage`** — Disable auto-staging of fixed files
- **`--pr`** — Check only files changed in the current PR/branch (shortcut for --from-ref DEFAULT_BRANCH --to-ref HEAD)
@@ -44,6 +49,23 @@ Runs each step's check command, which by convention only reports problems. If th
**Choices:** `git`, `patch-file`, `none`
- **`--stats`** — Display statistics about files matching each step
-- **`--to-ref `** — End reference for checking files (requires --from-ref)
+- **`--to-ref `** — End reference for comparison with --from-ref
- **`--unstaged`** — Run on unstaged and untracked files only (excludes staged files), without stashing. Useful for linting files an agent just changed.
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Check a full repository or inspect one step before running it.
+
+```sh
+hk check --all
+hk check --step eslint
+hk check --why eslint
+hk check --plan --json
+```
+
+## Learn more
+
+[Getting started](/getting_started) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/commands.json b/docs/cli/commands.json
index 272a99736..7df0b1234 100644
--- a/docs/cli/commands.json
+++ b/docs/cli/commands.json
@@ -357,8 +357,8 @@
{
"name": "all",
"usage": "-a --all",
- "help": "Run on all files instead of just staged files",
- "help_first_line": "Run on all files instead of just staged files",
+ "help": "Select all tracked and eligible untracked files, then apply step filters",
+ "help_first_line": "Select all tracked and eligible untracked files, then apply step filters",
"short": ["a"],
"long": ["all"],
"hide": false,
@@ -397,8 +397,8 @@
{
"name": "fix",
"usage": "-f --fix",
- "help": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
- "help_first_line": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
+ "help": "Run fix commands instead of check commands for this invocation",
+ "help_first_line": "Run fix commands instead of check commands for this invocation",
"short": ["f"],
"long": ["fix"],
"hide": false,
@@ -561,8 +561,8 @@
{
"name": "from-ref",
"usage": "--from-ref ",
- "help": "Start reference for checking files (requires --to-ref)",
- "help_first_line": "Start reference for checking files (requires --to-ref)",
+ "help": "Select files changed since this reference; optionally pair with --to-ref",
+ "help_first_line": "Select files changed since this reference; optionally pair with --to-ref",
"short": [],
"long": ["from-ref"],
"hide": false,
@@ -716,8 +716,8 @@
{
"name": "to-ref",
"usage": "--to-ref ",
- "help": "End reference for checking files (requires --from-ref)",
- "help_first_line": "End reference for checking files (requires --from-ref)",
+ "help": "End reference for comparison with --from-ref",
+ "help_first_line": "End reference for comparison with --from-ref",
"short": [],
"long": ["to-ref"],
"hide": false,
@@ -1086,8 +1086,8 @@
{
"name": "all",
"usage": "-a --all",
- "help": "Run on all files instead of just staged files",
- "help_first_line": "Run on all files instead of just staged files",
+ "help": "Select all tracked and eligible untracked files, then apply step filters",
+ "help_first_line": "Select all tracked and eligible untracked files, then apply step filters",
"short": ["a"],
"long": ["all"],
"hide": false,
@@ -1126,8 +1126,8 @@
{
"name": "fix",
"usage": "-f --fix",
- "help": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
- "help_first_line": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
+ "help": "Run fix commands instead of check commands for this invocation",
+ "help_first_line": "Run fix commands instead of check commands for this invocation",
"short": ["f"],
"long": ["fix"],
"hide": false,
@@ -1290,8 +1290,8 @@
{
"name": "from-ref",
"usage": "--from-ref ",
- "help": "Start reference for checking files (requires --to-ref)",
- "help_first_line": "Start reference for checking files (requires --to-ref)",
+ "help": "Select files changed since this reference; optionally pair with --to-ref",
+ "help_first_line": "Select files changed since this reference; optionally pair with --to-ref",
"short": [],
"long": ["from-ref"],
"hide": false,
@@ -1445,8 +1445,8 @@
{
"name": "to-ref",
"usage": "--to-ref ",
- "help": "End reference for checking files (requires --from-ref)",
- "help_first_line": "End reference for checking files (requires --from-ref)",
+ "help": "End reference for comparison with --from-ref",
+ "help_first_line": "End reference for comparison with --from-ref",
"short": [],
"long": ["to-ref"],
"hide": false,
@@ -1878,8 +1878,8 @@
{
"name": "all",
"usage": "-a --all",
- "help": "Run on all files instead of just staged files",
- "help_first_line": "Run on all files instead of just staged files",
+ "help": "Select all tracked and eligible untracked files, then apply step filters",
+ "help_first_line": "Select all tracked and eligible untracked files, then apply step filters",
"short": ["a"],
"long": ["all"],
"hide": false,
@@ -1918,8 +1918,8 @@
{
"name": "fix",
"usage": "-f --fix",
- "help": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
- "help_first_line": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
+ "help": "Run fix commands instead of check commands for this invocation",
+ "help_first_line": "Run fix commands instead of check commands for this invocation",
"short": ["f"],
"long": ["fix"],
"hide": false,
@@ -2082,8 +2082,8 @@
{
"name": "from-ref",
"usage": "--from-ref ",
- "help": "Start reference for checking files (requires --to-ref)",
- "help_first_line": "Start reference for checking files (requires --to-ref)",
+ "help": "Select files changed since this reference; optionally pair with --to-ref",
+ "help_first_line": "Select files changed since this reference; optionally pair with --to-ref",
"short": [],
"long": ["from-ref"],
"hide": false,
@@ -2237,8 +2237,8 @@
{
"name": "to-ref",
"usage": "--to-ref ",
- "help": "End reference for checking files (requires --from-ref)",
- "help_first_line": "End reference for checking files (requires --from-ref)",
+ "help": "End reference for comparison with --from-ref",
+ "help_first_line": "End reference for comparison with --from-ref",
"short": [],
"long": ["to-ref"],
"hide": false,
@@ -2366,8 +2366,8 @@
{
"name": "all",
"usage": "-a --all",
- "help": "Run on all files instead of just staged files",
- "help_first_line": "Run on all files instead of just staged files",
+ "help": "Select all tracked and eligible untracked files, then apply step filters",
+ "help_first_line": "Select all tracked and eligible untracked files, then apply step filters",
"short": ["a"],
"long": ["all"],
"hide": false,
@@ -2406,8 +2406,8 @@
{
"name": "fix",
"usage": "-f --fix",
- "help": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
- "help_first_line": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
+ "help": "Run fix commands instead of check commands for this invocation",
+ "help_first_line": "Run fix commands instead of check commands for this invocation",
"short": ["f"],
"long": ["fix"],
"hide": false,
@@ -2570,8 +2570,8 @@
{
"name": "from-ref",
"usage": "--from-ref ",
- "help": "Start reference for checking files (requires --to-ref)",
- "help_first_line": "Start reference for checking files (requires --to-ref)",
+ "help": "Select files changed since this reference; optionally pair with --to-ref",
+ "help_first_line": "Select files changed since this reference; optionally pair with --to-ref",
"short": [],
"long": ["from-ref"],
"hide": false,
@@ -2725,8 +2725,8 @@
{
"name": "to-ref",
"usage": "--to-ref ",
- "help": "End reference for checking files (requires --from-ref)",
- "help_first_line": "End reference for checking files (requires --from-ref)",
+ "help": "End reference for comparison with --from-ref",
+ "help_first_line": "End reference for comparison with --from-ref",
"short": [],
"long": ["to-ref"],
"hide": false,
@@ -2827,8 +2827,8 @@
{
"name": "all",
"usage": "-a --all",
- "help": "Run on all files instead of just staged files",
- "help_first_line": "Run on all files instead of just staged files",
+ "help": "Select all tracked and eligible untracked files, then apply step filters",
+ "help_first_line": "Select all tracked and eligible untracked files, then apply step filters",
"short": ["a"],
"long": ["all"],
"hide": false,
@@ -2867,8 +2867,8 @@
{
"name": "fix",
"usage": "-f --fix",
- "help": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
- "help_first_line": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
+ "help": "Run fix commands instead of check commands for this invocation",
+ "help_first_line": "Run fix commands instead of check commands for this invocation",
"short": ["f"],
"long": ["fix"],
"hide": false,
@@ -3031,8 +3031,8 @@
{
"name": "from-ref",
"usage": "--from-ref ",
- "help": "Start reference for checking files (requires --to-ref)",
- "help_first_line": "Start reference for checking files (requires --to-ref)",
+ "help": "Select files changed since this reference; optionally pair with --to-ref",
+ "help_first_line": "Select files changed since this reference; optionally pair with --to-ref",
"short": [],
"long": ["from-ref"],
"hide": false,
@@ -3186,8 +3186,8 @@
{
"name": "to-ref",
"usage": "--to-ref ",
- "help": "End reference for checking files (requires --from-ref)",
- "help_first_line": "End reference for checking files (requires --from-ref)",
+ "help": "End reference for comparison with --from-ref",
+ "help_first_line": "End reference for comparison with --from-ref",
"short": [],
"long": ["to-ref"],
"hide": false,
@@ -3297,8 +3297,8 @@
{
"name": "all",
"usage": "-a --all",
- "help": "Run on all files instead of just staged files",
- "help_first_line": "Run on all files instead of just staged files",
+ "help": "Select all tracked and eligible untracked files, then apply step filters",
+ "help_first_line": "Select all tracked and eligible untracked files, then apply step filters",
"short": ["a"],
"long": ["all"],
"hide": false,
@@ -3337,8 +3337,8 @@
{
"name": "fix",
"usage": "-f --fix",
- "help": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
- "help_first_line": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
+ "help": "Run fix commands instead of check commands for this invocation",
+ "help_first_line": "Run fix commands instead of check commands for this invocation",
"short": ["f"],
"long": ["fix"],
"hide": false,
@@ -3501,8 +3501,8 @@
{
"name": "from-ref",
"usage": "--from-ref ",
- "help": "Start reference for checking files (requires --to-ref)",
- "help_first_line": "Start reference for checking files (requires --to-ref)",
+ "help": "Select files changed since this reference; optionally pair with --to-ref",
+ "help_first_line": "Select files changed since this reference; optionally pair with --to-ref",
"short": [],
"long": ["from-ref"],
"hide": false,
@@ -3656,8 +3656,8 @@
{
"name": "to-ref",
"usage": "--to-ref ",
- "help": "End reference for checking files (requires --from-ref)",
- "help_first_line": "End reference for checking files (requires --from-ref)",
+ "help": "End reference for comparison with --from-ref",
+ "help_first_line": "End reference for comparison with --from-ref",
"short": [],
"long": ["to-ref"],
"hide": false,
@@ -3767,8 +3767,8 @@
{
"name": "all",
"usage": "-a --all",
- "help": "Run on all files instead of just staged files",
- "help_first_line": "Run on all files instead of just staged files",
+ "help": "Select all tracked and eligible untracked files, then apply step filters",
+ "help_first_line": "Select all tracked and eligible untracked files, then apply step filters",
"short": ["a"],
"long": ["all"],
"hide": false,
@@ -3807,8 +3807,8 @@
{
"name": "fix",
"usage": "-f --fix",
- "help": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
- "help_first_line": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
+ "help": "Run fix commands instead of check commands for this invocation",
+ "help_first_line": "Run fix commands instead of check commands for this invocation",
"short": ["f"],
"long": ["fix"],
"hide": false,
@@ -3971,8 +3971,8 @@
{
"name": "from-ref",
"usage": "--from-ref ",
- "help": "Start reference for checking files (requires --to-ref)",
- "help_first_line": "Start reference for checking files (requires --to-ref)",
+ "help": "Select files changed since this reference; optionally pair with --to-ref",
+ "help_first_line": "Select files changed since this reference; optionally pair with --to-ref",
"short": [],
"long": ["from-ref"],
"hide": false,
@@ -4126,8 +4126,8 @@
{
"name": "to-ref",
"usage": "--to-ref ",
- "help": "End reference for checking files (requires --from-ref)",
- "help_first_line": "End reference for checking files (requires --from-ref)",
+ "help": "End reference for comparison with --from-ref",
+ "help_first_line": "End reference for comparison with --from-ref",
"short": [],
"long": ["to-ref"],
"hide": false,
@@ -4228,8 +4228,8 @@
{
"name": "all",
"usage": "-a --all",
- "help": "Run on all files instead of just staged files",
- "help_first_line": "Run on all files instead of just staged files",
+ "help": "Select all tracked and eligible untracked files, then apply step filters",
+ "help_first_line": "Select all tracked and eligible untracked files, then apply step filters",
"short": ["a"],
"long": ["all"],
"hide": false,
@@ -4268,8 +4268,8 @@
{
"name": "fix",
"usage": "-f --fix",
- "help": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
- "help_first_line": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
+ "help": "Run fix commands instead of check commands for this invocation",
+ "help_first_line": "Run fix commands instead of check commands for this invocation",
"short": ["f"],
"long": ["fix"],
"hide": false,
@@ -4432,8 +4432,8 @@
{
"name": "from-ref",
"usage": "--from-ref ",
- "help": "Start reference for checking files (requires --to-ref)",
- "help_first_line": "Start reference for checking files (requires --to-ref)",
+ "help": "Select files changed since this reference; optionally pair with --to-ref",
+ "help_first_line": "Select files changed since this reference; optionally pair with --to-ref",
"short": [],
"long": ["from-ref"],
"hide": false,
@@ -4587,8 +4587,8 @@
{
"name": "to-ref",
"usage": "--to-ref ",
- "help": "End reference for checking files (requires --from-ref)",
- "help_first_line": "End reference for checking files (requires --from-ref)",
+ "help": "End reference for comparison with --from-ref",
+ "help_first_line": "End reference for comparison with --from-ref",
"short": [],
"long": ["to-ref"],
"hide": false,
@@ -4707,8 +4707,8 @@
{
"name": "all",
"usage": "-a --all",
- "help": "Run on all files instead of just staged files",
- "help_first_line": "Run on all files instead of just staged files",
+ "help": "Select all tracked and eligible untracked files, then apply step filters",
+ "help_first_line": "Select all tracked and eligible untracked files, then apply step filters",
"short": ["a"],
"long": ["all"],
"hide": false,
@@ -4747,8 +4747,8 @@
{
"name": "fix",
"usage": "-f --fix",
- "help": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
- "help_first_line": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
+ "help": "Run fix commands instead of check commands for this invocation",
+ "help_first_line": "Run fix commands instead of check commands for this invocation",
"short": ["f"],
"long": ["fix"],
"hide": false,
@@ -4911,8 +4911,8 @@
{
"name": "from-ref",
"usage": "--from-ref ",
- "help": "Start reference for checking files (requires --to-ref)",
- "help_first_line": "Start reference for checking files (requires --to-ref)",
+ "help": "Select files changed since this reference; optionally pair with --to-ref",
+ "help_first_line": "Select files changed since this reference; optionally pair with --to-ref",
"short": [],
"long": ["from-ref"],
"hide": false,
@@ -5066,8 +5066,8 @@
{
"name": "to-ref",
"usage": "--to-ref ",
- "help": "End reference for checking files (requires --from-ref)",
- "help_first_line": "End reference for checking files (requires --from-ref)",
+ "help": "End reference for comparison with --from-ref",
+ "help_first_line": "End reference for comparison with --from-ref",
"short": [],
"long": ["to-ref"],
"hide": false,
@@ -5186,8 +5186,8 @@
{
"name": "all",
"usage": "-a --all",
- "help": "Run on all files instead of just staged files",
- "help_first_line": "Run on all files instead of just staged files",
+ "help": "Select all tracked and eligible untracked files, then apply step filters",
+ "help_first_line": "Select all tracked and eligible untracked files, then apply step filters",
"short": ["a"],
"long": ["all"],
"hide": false,
@@ -5226,8 +5226,8 @@
{
"name": "fix",
"usage": "-f --fix",
- "help": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
- "help_first_line": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
+ "help": "Run fix commands instead of check commands for this invocation",
+ "help_first_line": "Run fix commands instead of check commands for this invocation",
"short": ["f"],
"long": ["fix"],
"hide": false,
@@ -5390,8 +5390,8 @@
{
"name": "from-ref",
"usage": "--from-ref ",
- "help": "Start reference for checking files (requires --to-ref)",
- "help_first_line": "Start reference for checking files (requires --to-ref)",
+ "help": "Select files changed since this reference; optionally pair with --to-ref",
+ "help_first_line": "Select files changed since this reference; optionally pair with --to-ref",
"short": [],
"long": ["from-ref"],
"hide": false,
@@ -5545,8 +5545,8 @@
{
"name": "to-ref",
"usage": "--to-ref ",
- "help": "End reference for checking files (requires --from-ref)",
- "help_first_line": "End reference for checking files (requires --from-ref)",
+ "help": "End reference for comparison with --from-ref",
+ "help_first_line": "End reference for comparison with --from-ref",
"short": [],
"long": ["to-ref"],
"hide": false,
@@ -5674,8 +5674,8 @@
{
"name": "all",
"usage": "-a --all",
- "help": "Run on all files instead of just staged files",
- "help_first_line": "Run on all files instead of just staged files",
+ "help": "Select all tracked and eligible untracked files, then apply step filters",
+ "help_first_line": "Select all tracked and eligible untracked files, then apply step filters",
"short": ["a"],
"long": ["all"],
"hide": false,
@@ -5714,8 +5714,8 @@
{
"name": "fix",
"usage": "-f --fix",
- "help": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
- "help_first_line": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
+ "help": "Run fix commands instead of check commands for this invocation",
+ "help_first_line": "Run fix commands instead of check commands for this invocation",
"short": ["f"],
"long": ["fix"],
"hide": false,
@@ -5878,8 +5878,8 @@
{
"name": "from-ref",
"usage": "--from-ref ",
- "help": "Start reference for checking files (requires --to-ref)",
- "help_first_line": "Start reference for checking files (requires --to-ref)",
+ "help": "Select files changed since this reference; optionally pair with --to-ref",
+ "help_first_line": "Select files changed since this reference; optionally pair with --to-ref",
"short": [],
"long": ["from-ref"],
"hide": false,
@@ -6033,8 +6033,8 @@
{
"name": "to-ref",
"usage": "--to-ref ",
- "help": "End reference for checking files (requires --from-ref)",
- "help_first_line": "End reference for checking files (requires --from-ref)",
+ "help": "End reference for comparison with --from-ref",
+ "help_first_line": "End reference for comparison with --from-ref",
"short": [],
"long": ["to-ref"],
"hide": false,
@@ -6139,8 +6139,8 @@
{
"name": "all",
"usage": "-a --all",
- "help": "Run on all files instead of just staged files",
- "help_first_line": "Run on all files instead of just staged files",
+ "help": "Select all tracked and eligible untracked files, then apply step filters",
+ "help_first_line": "Select all tracked and eligible untracked files, then apply step filters",
"short": ["a"],
"long": ["all"],
"hide": false,
@@ -6179,8 +6179,8 @@
{
"name": "fix",
"usage": "-f --fix",
- "help": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
- "help_first_line": "Run fix command instead of check command (this is the default behavior unless HK_FIX=0)",
+ "help": "Run fix commands instead of check commands for this invocation",
+ "help_first_line": "Run fix commands instead of check commands for this invocation",
"short": ["f"],
"long": ["fix"],
"hide": false,
@@ -6343,8 +6343,8 @@
{
"name": "from-ref",
"usage": "--from-ref ",
- "help": "Start reference for checking files (requires --to-ref)",
- "help_first_line": "Start reference for checking files (requires --to-ref)",
+ "help": "Select files changed since this reference; optionally pair with --to-ref",
+ "help_first_line": "Select files changed since this reference; optionally pair with --to-ref",
"short": [],
"long": ["from-ref"],
"hide": false,
@@ -6498,8 +6498,8 @@
{
"name": "to-ref",
"usage": "--to-ref ",
- "help": "End reference for checking files (requires --from-ref)",
- "help_first_line": "End reference for checking files (requires --from-ref)",
+ "help": "End reference for comparison with --from-ref",
+ "help_first_line": "End reference for comparison with --from-ref",
"short": [],
"long": ["to-ref"],
"hide": false,
diff --git a/docs/cli/completion.md b/docs/cli/completion.md
index d55e17bbe..db88b9000 100644
--- a/docs/cli/completion.md
+++ b/docs/cli/completion.md
@@ -1,3 +1,8 @@
+---
+title: "hk completion"
+description: "Generate a completion script, or install it in the location used by your shell. hk prints any remaining shell setup instructions."
+---
+
# `hk completion`
@@ -24,3 +29,19 @@ Generate shell completion scripts
**Effect:** modifies state
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Generate a completion script, or install it in the location used by your shell. hk prints any remaining shell setup instructions.
+
+```sh
+hk completion bash
+hk completion zsh --install
+hk completion fish --install
+```
+
+## Learn more
+
+[Getting started](/getting_started) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/config.md b/docs/cli/config.md
index 677c90416..f8d2831d0 100644
--- a/docs/cli/config.md
+++ b/docs/cli/config.md
@@ -1,3 +1,8 @@
+---
+title: "hk config"
+description: "Inspect effective runtime settings and their sources. Use hk check --plan for hook and step selection."
+---
+
# `hk config`
@@ -20,3 +25,19 @@ View hk's effective configuration and where each value comes from. Configuration
- [`hk config explain `](/cli/config/explain.md)
- [`hk config get `](/cli/config/get.md)
- [`hk config sources`](/cli/config/sources.md)
+
+
+
+## Examples
+
+Inspect effective runtime settings and their sources. Use hk check --plan for hook and step selection.
+
+```sh
+hk config dump
+hk config get jobs
+hk config explain jobs
+```
+
+## Learn more
+
+[Configuration guide](/configuration) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/config/dump.md b/docs/cli/config/dump.md
index e58c28c1b..d887a263a 100644
--- a/docs/cli/config/dump.md
+++ b/docs/cli/config/dump.md
@@ -1,3 +1,8 @@
+---
+title: "hk config dump"
+description: "Choose JSON or TOML for the effective runtime settings."
+---
+
# `hk config dump`
@@ -18,3 +23,18 @@ Shows the merged configuration from all sources, including CLI flags, environmen
**Default:** `json`
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Choose JSON or TOML for the effective runtime settings.
+
+```sh
+hk config dump
+hk config dump --format toml
+```
+
+## Learn more
+
+[Configuration guide](/configuration) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/config/explain.md b/docs/cli/config/explain.md
index 5d0128b46..ef7d0052a 100644
--- a/docs/cli/config/explain.md
+++ b/docs/cli/config/explain.md
@@ -1,3 +1,8 @@
+---
+title: "hk config explain"
+description: "Find which source supplies a runtime setting."
+---
+
# `hk config explain`
@@ -16,3 +21,18 @@ Shows the resolved value, the source it came from (cli, env, git, pkl, or defaul
## Flags
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Find which source supplies a runtime setting.
+
+```sh
+hk config explain jobs
+hk config explain exclude
+```
+
+## Learn more
+
+[Configuration guide](/configuration) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/config/get.md b/docs/cli/config/get.md
index 7cbcb4d7d..97d245410 100644
--- a/docs/cli/config/get.md
+++ b/docs/cli/config/get.md
@@ -1,3 +1,8 @@
+---
+title: "hk config get"
+description: "Read an individual runtime setting by its underscore-separated name."
+---
+
# `hk config get`
@@ -16,3 +21,18 @@ Accepts any setting name shown by `hk config dump` (for example fail_fast, exclu
## Flags
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Read an individual runtime setting by its underscore-separated name.
+
+```sh
+hk config get jobs
+hk config get skip_steps
+```
+
+## Learn more
+
+[Configuration guide](/configuration) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/config/sources.md b/docs/cli/config/sources.md
index d36497a80..7360536e6 100644
--- a/docs/cli/config/sources.md
+++ b/docs/cli/config/sources.md
@@ -1,3 +1,8 @@
+---
+title: "hk config sources"
+description: "Show precedence, then inspect the resolved values."
+---
+
# `hk config sources`
@@ -12,3 +17,18 @@ Lists every configuration source from highest to lowest precedence.
## Flags
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Show precedence, then inspect the resolved values.
+
+```sh
+hk config sources
+hk config dump
+```
+
+## Learn more
+
+[Configuration guide](/configuration) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/fix.md b/docs/cli/fix.md
index 6ba863ff3..c57587dd0 100644
--- a/docs/cli/fix.md
+++ b/docs/cli/fix.md
@@ -1,3 +1,8 @@
+---
+title: "hk fix"
+description: "Fix commands may also stage changes. Use --no-stage to leave fixes unstaged, then review both working-tree and staged diffs."
+---
+
# `hk fix`
@@ -15,10 +20,10 @@ Runs each step's fix command to modify files in place. Passing `--check`, or set
## Flags
-- **`-a --all`** — Run on all files instead of just staged files
+- **`-a --all`** — Select all tracked and eligible untracked files, then apply step filters
- **`-c --check`** — Run check command instead of fix command
- **`-e --exclude `** — Exclude files that otherwise would have been selected
-- **`-f --fix`** — Run fix command instead of check command (this is the default behavior unless HK_FIX=0)
+- **`-f --fix`** — Run fix commands instead of check commands for this invocation
- **`-g --glob `** — Run on files that match these glob patterns
- **`-J --json`** — Output the plan as JSON when combined with --plan or --why
- **`-P --plan`** — Print the plan instead of running the hook
@@ -30,7 +35,7 @@ Runs each step's fix command to modify files in place. Passing `--check`, or set
**Choices:** `human`, `json`, `jsonl`
-- **`--from-ref `** — Start reference for checking files (requires --to-ref)
+- **`--from-ref `** — Select files changed since this reference; optionally pair with --to-ref
- **`--no-fail-fast`** — Continue on failures (opposite of --fail-fast)
- **`--no-stage`** — Disable auto-staging of fixed files
- **`--pr`** — Check only files changed in the current PR/branch (shortcut for --from-ref DEFAULT_BRANCH --to-ref HEAD)
@@ -44,6 +49,24 @@ Runs each step's fix command to modify files in place. Passing `--check`, or set
**Choices:** `git`, `patch-file`, `none`
- **`--stats`** — Display statistics about files matching each step
-- **`--to-ref `** — End reference for checking files (requires --from-ref)
+- **`--to-ref `** — End reference for comparison with --from-ref
- **`--unstaged`** — Run on unstaged and untracked files only (excludes staged files), without stashing. Useful for linting files an agent just changed.
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Fix commands may also stage changes. Use --no-stage to leave fixes unstaged, then review both working-tree and staged diffs.
+
+```sh
+hk fix --no-stage
+hk fix --all --no-stage
+hk fix --step prettier
+git diff
+git diff --cached
+```
+
+## Learn more
+
+[Getting started](/getting_started) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/index.md b/docs/cli/index.md
index 84423c216..6ebd0fa55 100644
--- a/docs/cli/index.md
+++ b/docs/cli/index.md
@@ -1,6 +1,25 @@
+---
+title: "CLI reference"
+description: "Commands, options, and examples for the hk CLI."
+---
+
-# `hk`
+# CLI reference
+
+Run the same configured steps locally, in Git hooks, and in CI.
+
+| Task | Command |
+| ------------------------ | --------------------------------------------------------- |
+| Set up a project | [hk init](/cli/init) and [hk install](/cli/install) |
+| Check or fix code | [hk check](/cli/check) and [hk fix](/cli/fix) |
+| Run a named hook | [hk run](/cli/run) |
+| Inspect configuration | [hk config](/cli/config) and [hk validate](/cli/validate) |
+| Test a step definition | [hk test](/cli/test) |
+| Integrate a coding agent | [hk agent](/cli/agent) and [hk mcp](/cli/mcp) |
+| Use included file checks | [hk util](/cli/util) |
+
+Start with [getting started](/getting_started) for an end-to-end setup. Command-specific flags are listed on each page; the flags below apply globally.
**Usage:** `hk [FLAGS] `
@@ -85,3 +104,9 @@
- [`hk util trailing-whitespace [-d --diff] [-f --fix] …`](/cli/util/trailing-whitespace.md)
- [`hk validate`](/cli/validate.md)
- [`hk version`](/cli/version.md)
+
+
+
+## Learn more
+
+[Getting started](/getting_started) · [Troubleshooting](/logging)
diff --git a/docs/cli/init.md b/docs/cli/init.md
index f9b3d5348..d17ac6928 100644
--- a/docs/cli/init.md
+++ b/docs/cli/init.md
@@ -1,3 +1,8 @@
+---
+title: "hk init"
+description: "Generate a configuration, then validate it. Install selected tools separately."
+---
+
# `hk init`
@@ -17,3 +22,21 @@ Generate a new hk.pkl file for a project
Set HK_MISE=1 to make this the default behavior.
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Generate a configuration, then validate it. Install selected tools separately.
+
+```sh
+hk init
+hk validate
+
+# Choose linters and hooks interactively
+hk init --interactive
+```
+
+## Learn more
+
+[Getting started](/getting_started) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/install.md b/docs/cli/install.md
index 32dc399f5..141c46184 100644
--- a/docs/cli/install.md
+++ b/docs/cli/install.md
@@ -1,3 +1,8 @@
+---
+title: "hk install"
+description: "Choose a local installation or a global one on Git 2.54+. Add --mise when Git needs mise to provide tools."
+---
+
# `hk install`
@@ -31,3 +36,20 @@ If hk is already configured globally (any `hook.hk-*` entry in `~/.gitconfig`),
Set HK_MISE=1 to make this the default behavior.
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Choose a local installation or a global one on Git 2.54+. Add --mise when Git needs mise to provide tools.
+
+```sh
+hk install
+
+# Alternative: install once for all repositories
+hk install --global
+```
+
+## Learn more
+
+[Getting started](/getting_started) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/mcp.md b/docs/cli/mcp.md
index 85517dba3..17130facf 100644
--- a/docs/cli/mcp.md
+++ b/docs/cli/mcp.md
@@ -1,3 +1,8 @@
+---
+title: "hk mcp"
+description: "Start the STDIO MCP server with a fixed project root. Configure your host to launch this command."
+---
+
# `hk mcp`
@@ -10,3 +15,17 @@ Run an MCP server for coding agents over standard input/output
- **`--root `** — Restrict hk tools to this project root (defaults to the current directory)
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Start the STDIO MCP server with a fixed project root. Configure your host to launch this command.
+
+```sh
+hk mcp --root /absolute/path/to/project
+```
+
+## Learn more
+
+[Coding agents](/agents) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/migrate.md b/docs/cli/migrate.md
index 79c057045..e5fdf95df 100644
--- a/docs/cli/migrate.md
+++ b/docs/cli/migrate.md
@@ -1,3 +1,8 @@
+---
+title: "hk migrate"
+description: "Convert a pre-commit configuration, then review the generated file and tool requirements."
+---
+
# `hk migrate`
@@ -14,3 +19,18 @@ Migrate from other hook managers to hk
## Subcommands
- [`hk migrate pre-commit [FLAGS]`](/cli/migrate/pre-commit.md)
+
+
+
+## Examples
+
+Convert a pre-commit configuration, then review the generated file and tool requirements.
+
+```sh
+hk migrate pre-commit --output hk.migrated.pkl
+HK_FILE=./hk.migrated.pkl hk validate
+```
+
+## Learn more
+
+[Getting started](/getting_started) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/migrate/pre-commit.md b/docs/cli/migrate/pre-commit.md
index 98c2486ba..77f2ca240 100644
--- a/docs/cli/migrate/pre-commit.md
+++ b/docs/cli/migrate/pre-commit.md
@@ -1,3 +1,8 @@
+---
+title: "hk migrate pre-commit"
+description: "Write to a separate file while evaluating a migration. Review filters, tool versions, and any unsupported hooks before installing."
+---
+
# `hk migrate pre-commit`
@@ -20,3 +25,18 @@ Migrate from pre-commit to hk
- **`--hk-pkl-root `** — Root path for hk pkl files (e.g. "pkl" for a local checkout, or a package URL prefix). If set, the generated config uses {root}/Config.pkl and {root}/Builtins.pkl
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Write to a separate file while evaluating a migration. Review filters, tool versions, and any unsupported hooks before installing.
+
+```sh
+hk migrate pre-commit --output hk.migrated.pkl
+HK_FILE=./hk.migrated.pkl hk check --all --plan
+```
+
+## Learn more
+
+[Getting started](/getting_started) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/run.md b/docs/cli/run.md
index 503aa3ab2..107d606b6 100644
--- a/docs/cli/run.md
+++ b/docs/cli/run.md
@@ -1,3 +1,8 @@
+---
+title: "hk run"
+description: "Run a configured hook by name. A plan uses its settings without executing its steps."
+---
+
# `hk run`
@@ -13,10 +18,10 @@ Run a hook
## Flags
-- **`-a --all`** — Run on all files instead of just staged files
+- **`-a --all`** — Select all tracked and eligible untracked files, then apply step filters
- **`-c --check`** — Run check command instead of fix command
- **`-e --exclude `** — Exclude files that otherwise would have been selected
-- **`-f --fix`** — Run fix command instead of check command (this is the default behavior unless HK_FIX=0)
+- **`-f --fix`** — Run fix commands instead of check commands for this invocation
- **`-g --glob `** — Run on files that match these glob patterns
- **`-J --json`** — Output the plan as JSON when combined with --plan or --why
- **`-P --plan`** — Print the plan instead of running the hook
@@ -28,7 +33,7 @@ Run a hook
**Choices:** `human`, `json`, `jsonl`
-- **`--from-ref `** — Start reference for checking files (requires --to-ref)
+- **`--from-ref `** — Select files changed since this reference; optionally pair with --to-ref
- **`--no-fail-fast`** — Continue on failures (opposite of --fail-fast)
- **`--no-stage`** — Disable auto-staging of fixed files
- **`--pr`** — Check only files changed in the current PR/branch (shortcut for --from-ref DEFAULT_BRANCH --to-ref HEAD)
@@ -42,7 +47,7 @@ Run a hook
**Choices:** `git`, `patch-file`, `none`
- **`--stats`** — Display statistics about files matching each step
-- **`--to-ref `** — End reference for checking files (requires --from-ref)
+- **`--to-ref `** — End reference for comparison with --from-ref
- **`--unstaged`** — Run on unstaged and untracked files only (excludes staged files), without stashing. Useful for linting files an agent just changed.
- **`-h --help`** — Print help
@@ -57,3 +62,21 @@ Run a hook
- [`hk run pre-push [FLAGS] [ARGS]…`](/cli/run/pre-push.md)
- [`hk run pre-rebase [FLAGS] …`](/cli/run/pre-rebase.md)
- [`hk run prepare-commit-msg [FLAGS] …`](/cli/run/prepare-commit-msg.md)
+
+
+
+## Examples
+
+Run a configured hook by name. A plan uses its settings without executing its steps.
+
+```sh
+hk run pre-commit --plan
+hk run pre-commit
+
+# A custom hook defined in hk.pkl
+hk run my-hook
+```
+
+## Learn more
+
+[Getting started](/getting_started) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/run/commit-msg.md b/docs/cli/run/commit-msg.md
index 1cb68208d..32ee3165e 100644
--- a/docs/cli/run/commit-msg.md
+++ b/docs/cli/run/commit-msg.md
@@ -1,3 +1,8 @@
+---
+title: "hk run commit-msg"
+description: "Arguments and options for hk run commit-msg."
+---
+
# `hk run commit-msg`
@@ -14,10 +19,10 @@ Run the commit-msg hook
## Flags
-- **`-a --all`** — Run on all files instead of just staged files
+- **`-a --all`** — Select all tracked and eligible untracked files, then apply step filters
- **`-c --check`** — Run check command instead of fix command
- **`-e --exclude `** — Exclude files that otherwise would have been selected
-- **`-f --fix`** — Run fix command instead of check command (this is the default behavior unless HK_FIX=0)
+- **`-f --fix`** — Run fix commands instead of check commands for this invocation
- **`-g --glob `** — Run on files that match these glob patterns
- **`-J --json`** — Output the plan as JSON when combined with --plan or --why
- **`-P --plan`** — Print the plan instead of running the hook
@@ -29,7 +34,7 @@ Run the commit-msg hook
**Choices:** `human`, `json`, `jsonl`
-- **`--from-ref `** — Start reference for checking files (requires --to-ref)
+- **`--from-ref `** — Select files changed since this reference; optionally pair with --to-ref
- **`--no-fail-fast`** — Continue on failures (opposite of --fail-fast)
- **`--no-stage`** — Disable auto-staging of fixed files
- **`--pr`** — Check only files changed in the current PR/branch (shortcut for --from-ref DEFAULT_BRANCH --to-ref HEAD)
@@ -43,6 +48,12 @@ Run the commit-msg hook
**Choices:** `git`, `patch-file`, `none`
- **`--stats`** — Display statistics about files matching each step
-- **`--to-ref `** — End reference for checking files (requires --from-ref)
+- **`--to-ref `** — End reference for comparison with --from-ref
- **`--unstaged`** — Run on unstaged and untracked files only (excludes staged files), without stashing. Useful for linting files an agent just changed.
- **`-h --help`** — Print help
+
+
+
+## Learn more
+
+[Git hooks and stashing](/hooks) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/run/post-checkout.md b/docs/cli/run/post-checkout.md
index 4ce543462..547635641 100644
--- a/docs/cli/run/post-checkout.md
+++ b/docs/cli/run/post-checkout.md
@@ -1,3 +1,8 @@
+---
+title: "hk run post-checkout"
+description: "Arguments and options for hk run post-checkout."
+---
+
# `hk run post-checkout`
@@ -15,10 +20,10 @@ Run the post-checkout hook
## Flags
-- **`-a --all`** — Run on all files instead of just staged files
+- **`-a --all`** — Select all tracked and eligible untracked files, then apply step filters
- **`-c --check`** — Run check command instead of fix command
- **`-e --exclude `** — Exclude files that otherwise would have been selected
-- **`-f --fix`** — Run fix command instead of check command (this is the default behavior unless HK_FIX=0)
+- **`-f --fix`** — Run fix commands instead of check commands for this invocation
- **`-g --glob `** — Run on files that match these glob patterns
- **`-J --json`** — Output the plan as JSON when combined with --plan or --why
- **`-P --plan`** — Print the plan instead of running the hook
@@ -30,7 +35,7 @@ Run the post-checkout hook
**Choices:** `human`, `json`, `jsonl`
-- **`--from-ref `** — Start reference for checking files (requires --to-ref)
+- **`--from-ref `** — Select files changed since this reference; optionally pair with --to-ref
- **`--no-fail-fast`** — Continue on failures (opposite of --fail-fast)
- **`--no-stage`** — Disable auto-staging of fixed files
- **`--pr`** — Check only files changed in the current PR/branch (shortcut for --from-ref DEFAULT_BRANCH --to-ref HEAD)
@@ -44,6 +49,12 @@ Run the post-checkout hook
**Choices:** `git`, `patch-file`, `none`
- **`--stats`** — Display statistics about files matching each step
-- **`--to-ref `** — End reference for checking files (requires --from-ref)
+- **`--to-ref `** — End reference for comparison with --from-ref
- **`--unstaged`** — Run on unstaged and untracked files only (excludes staged files), without stashing. Useful for linting files an agent just changed.
- **`-h --help`** — Print help
+
+
+
+## Learn more
+
+[Git hooks and stashing](/hooks) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/run/post-commit.md b/docs/cli/run/post-commit.md
index c6b4e34de..ceb7cecd4 100644
--- a/docs/cli/run/post-commit.md
+++ b/docs/cli/run/post-commit.md
@@ -1,3 +1,8 @@
+---
+title: "hk run post-commit"
+description: "Arguments and options for hk run post-commit."
+---
+
# `hk run post-commit`
@@ -12,10 +17,10 @@ Run the post-commit hook
## Flags
-- **`-a --all`** — Run on all files instead of just staged files
+- **`-a --all`** — Select all tracked and eligible untracked files, then apply step filters
- **`-c --check`** — Run check command instead of fix command
- **`-e --exclude `** — Exclude files that otherwise would have been selected
-- **`-f --fix`** — Run fix command instead of check command (this is the default behavior unless HK_FIX=0)
+- **`-f --fix`** — Run fix commands instead of check commands for this invocation
- **`-g --glob `** — Run on files that match these glob patterns
- **`-J --json`** — Output the plan as JSON when combined with --plan or --why
- **`-P --plan`** — Print the plan instead of running the hook
@@ -27,7 +32,7 @@ Run the post-commit hook
**Choices:** `human`, `json`, `jsonl`
-- **`--from-ref `** — Start reference for checking files (requires --to-ref)
+- **`--from-ref `** — Select files changed since this reference; optionally pair with --to-ref
- **`--no-fail-fast`** — Continue on failures (opposite of --fail-fast)
- **`--no-stage`** — Disable auto-staging of fixed files
- **`--pr`** — Check only files changed in the current PR/branch (shortcut for --from-ref DEFAULT_BRANCH --to-ref HEAD)
@@ -41,6 +46,12 @@ Run the post-commit hook
**Choices:** `git`, `patch-file`, `none`
- **`--stats`** — Display statistics about files matching each step
-- **`--to-ref `** — End reference for checking files (requires --from-ref)
+- **`--to-ref `** — End reference for comparison with --from-ref
- **`--unstaged`** — Run on unstaged and untracked files only (excludes staged files), without stashing. Useful for linting files an agent just changed.
- **`-h --help`** — Print help
+
+
+
+## Learn more
+
+[Git hooks and stashing](/hooks) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/run/post-merge.md b/docs/cli/run/post-merge.md
index 2a743e3a3..fe59676e1 100644
--- a/docs/cli/run/post-merge.md
+++ b/docs/cli/run/post-merge.md
@@ -1,3 +1,8 @@
+---
+title: "hk run post-merge"
+description: "Arguments and options for hk run post-merge."
+---
+
# `hk run post-merge`
@@ -13,10 +18,10 @@ Run the post-merge hook
## Flags
-- **`-a --all`** — Run on all files instead of just staged files
+- **`-a --all`** — Select all tracked and eligible untracked files, then apply step filters
- **`-c --check`** — Run check command instead of fix command
- **`-e --exclude `** — Exclude files that otherwise would have been selected
-- **`-f --fix`** — Run fix command instead of check command (this is the default behavior unless HK_FIX=0)
+- **`-f --fix`** — Run fix commands instead of check commands for this invocation
- **`-g --glob `** — Run on files that match these glob patterns
- **`-J --json`** — Output the plan as JSON when combined with --plan or --why
- **`-P --plan`** — Print the plan instead of running the hook
@@ -28,7 +33,7 @@ Run the post-merge hook
**Choices:** `human`, `json`, `jsonl`
-- **`--from-ref `** — Start reference for checking files (requires --to-ref)
+- **`--from-ref `** — Select files changed since this reference; optionally pair with --to-ref
- **`--no-fail-fast`** — Continue on failures (opposite of --fail-fast)
- **`--no-stage`** — Disable auto-staging of fixed files
- **`--pr`** — Check only files changed in the current PR/branch (shortcut for --from-ref DEFAULT_BRANCH --to-ref HEAD)
@@ -42,6 +47,12 @@ Run the post-merge hook
**Choices:** `git`, `patch-file`, `none`
- **`--stats`** — Display statistics about files matching each step
-- **`--to-ref `** — End reference for checking files (requires --from-ref)
+- **`--to-ref `** — End reference for comparison with --from-ref
- **`--unstaged`** — Run on unstaged and untracked files only (excludes staged files), without stashing. Useful for linting files an agent just changed.
- **`-h --help`** — Print help
+
+
+
+## Learn more
+
+[Git hooks and stashing](/hooks) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/run/post-rewrite.md b/docs/cli/run/post-rewrite.md
index e8cd2e26a..c58dd11d8 100644
--- a/docs/cli/run/post-rewrite.md
+++ b/docs/cli/run/post-rewrite.md
@@ -1,3 +1,8 @@
+---
+title: "hk run post-rewrite"
+description: "Arguments and options for hk run post-rewrite."
+---
+
# `hk run post-rewrite`
@@ -13,10 +18,10 @@ Run the post-rewrite hook
## Flags
-- **`-a --all`** — Run on all files instead of just staged files
+- **`-a --all`** — Select all tracked and eligible untracked files, then apply step filters
- **`-c --check`** — Run check command instead of fix command
- **`-e --exclude `** — Exclude files that otherwise would have been selected
-- **`-f --fix`** — Run fix command instead of check command (this is the default behavior unless HK_FIX=0)
+- **`-f --fix`** — Run fix commands instead of check commands for this invocation
- **`-g --glob `** — Run on files that match these glob patterns
- **`-J --json`** — Output the plan as JSON when combined with --plan or --why
- **`-P --plan`** — Print the plan instead of running the hook
@@ -28,7 +33,7 @@ Run the post-rewrite hook
**Choices:** `human`, `json`, `jsonl`
-- **`--from-ref `** — Start reference for checking files (requires --to-ref)
+- **`--from-ref `** — Select files changed since this reference; optionally pair with --to-ref
- **`--no-fail-fast`** — Continue on failures (opposite of --fail-fast)
- **`--no-stage`** — Disable auto-staging of fixed files
- **`--pr`** — Check only files changed in the current PR/branch (shortcut for --from-ref DEFAULT_BRANCH --to-ref HEAD)
@@ -42,6 +47,12 @@ Run the post-rewrite hook
**Choices:** `git`, `patch-file`, `none`
- **`--stats`** — Display statistics about files matching each step
-- **`--to-ref `** — End reference for checking files (requires --from-ref)
+- **`--to-ref `** — End reference for comparison with --from-ref
- **`--unstaged`** — Run on unstaged and untracked files only (excludes staged files), without stashing. Useful for linting files an agent just changed.
- **`-h --help`** — Print help
+
+
+
+## Learn more
+
+[Git hooks and stashing](/hooks) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/run/pre-commit.md b/docs/cli/run/pre-commit.md
index 025907488..45c34e74e 100644
--- a/docs/cli/run/pre-commit.md
+++ b/docs/cli/run/pre-commit.md
@@ -1,3 +1,8 @@
+---
+title: "hk run pre-commit"
+description: "Inspect the hook first. An actual run can fix, stage, and stash files according to its configuration."
+---
+
# `hk run pre-commit`
@@ -13,10 +18,10 @@ Run the pre-commit hook
## Flags
-- **`-a --all`** — Run on all files instead of just staged files
+- **`-a --all`** — Select all tracked and eligible untracked files, then apply step filters
- **`-c --check`** — Run check command instead of fix command
- **`-e --exclude `** — Exclude files that otherwise would have been selected
-- **`-f --fix`** — Run fix command instead of check command (this is the default behavior unless HK_FIX=0)
+- **`-f --fix`** — Run fix commands instead of check commands for this invocation
- **`-g --glob `** — Run on files that match these glob patterns
- **`-J --json`** — Output the plan as JSON when combined with --plan or --why
- **`-P --plan`** — Print the plan instead of running the hook
@@ -28,7 +33,7 @@ Run the pre-commit hook
**Choices:** `human`, `json`, `jsonl`
-- **`--from-ref `** — Start reference for checking files (requires --to-ref)
+- **`--from-ref `** — Select files changed since this reference; optionally pair with --to-ref
- **`--no-fail-fast`** — Continue on failures (opposite of --fail-fast)
- **`--no-stage`** — Disable auto-staging of fixed files
- **`--pr`** — Check only files changed in the current PR/branch (shortcut for --from-ref DEFAULT_BRANCH --to-ref HEAD)
@@ -42,6 +47,21 @@ Run the pre-commit hook
**Choices:** `git`, `patch-file`, `none`
- **`--stats`** — Display statistics about files matching each step
-- **`--to-ref `** — End reference for checking files (requires --from-ref)
+- **`--to-ref `** — End reference for comparison with --from-ref
- **`--unstaged`** — Run on unstaged and untracked files only (excludes staged files), without stashing. Useful for linting files an agent just changed.
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Inspect the hook first. An actual run can fix, stage, and stash files according to its configuration.
+
+```sh
+hk run pre-commit --plan
+hk run pre-commit
+```
+
+## Learn more
+
+[Git hooks and stashing](/hooks) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/run/pre-push.md b/docs/cli/run/pre-push.md
index 7b332e754..9c26cd408 100644
--- a/docs/cli/run/pre-push.md
+++ b/docs/cli/run/pre-push.md
@@ -1,3 +1,8 @@
+---
+title: "hk run pre-push"
+description: "Arguments and options for hk run pre-push."
+---
+
# `hk run pre-push`
@@ -15,10 +20,10 @@ Run the pre-push hook
## Flags
-- **`-a --all`** — Run on all files instead of just staged files
+- **`-a --all`** — Select all tracked and eligible untracked files, then apply step filters
- **`-c --check`** — Run check command instead of fix command
- **`-e --exclude `** — Exclude files that otherwise would have been selected
-- **`-f --fix`** — Run fix command instead of check command (this is the default behavior unless HK_FIX=0)
+- **`-f --fix`** — Run fix commands instead of check commands for this invocation
- **`-g --glob `** — Run on files that match these glob patterns
- **`-J --json`** — Output the plan as JSON when combined with --plan or --why
- **`-P --plan`** — Print the plan instead of running the hook
@@ -30,7 +35,7 @@ Run the pre-push hook
**Choices:** `human`, `json`, `jsonl`
-- **`--from-ref `** — Start reference for checking files (requires --to-ref)
+- **`--from-ref `** — Select files changed since this reference; optionally pair with --to-ref
- **`--no-fail-fast`** — Continue on failures (opposite of --fail-fast)
- **`--no-stage`** — Disable auto-staging of fixed files
- **`--pr`** — Check only files changed in the current PR/branch (shortcut for --from-ref DEFAULT_BRANCH --to-ref HEAD)
@@ -44,6 +49,12 @@ Run the pre-push hook
**Choices:** `git`, `patch-file`, `none`
- **`--stats`** — Display statistics about files matching each step
-- **`--to-ref `** — End reference for checking files (requires --from-ref)
+- **`--to-ref `** — End reference for comparison with --from-ref
- **`--unstaged`** — Run on unstaged and untracked files only (excludes staged files), without stashing. Useful for linting files an agent just changed.
- **`-h --help`** — Print help
+
+
+
+## Learn more
+
+[Git hooks and stashing](/hooks) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/run/pre-rebase.md b/docs/cli/run/pre-rebase.md
index 401053aed..804a36684 100644
--- a/docs/cli/run/pre-rebase.md
+++ b/docs/cli/run/pre-rebase.md
@@ -1,3 +1,8 @@
+---
+title: "hk run pre-rebase"
+description: "Arguments and options for hk run pre-rebase."
+---
+
# `hk run pre-rebase`
@@ -14,10 +19,10 @@ Run the pre-rebase hook
## Flags
-- **`-a --all`** — Run on all files instead of just staged files
+- **`-a --all`** — Select all tracked and eligible untracked files, then apply step filters
- **`-c --check`** — Run check command instead of fix command
- **`-e --exclude `** — Exclude files that otherwise would have been selected
-- **`-f --fix`** — Run fix command instead of check command (this is the default behavior unless HK_FIX=0)
+- **`-f --fix`** — Run fix commands instead of check commands for this invocation
- **`-g --glob `** — Run on files that match these glob patterns
- **`-J --json`** — Output the plan as JSON when combined with --plan or --why
- **`-P --plan`** — Print the plan instead of running the hook
@@ -29,7 +34,7 @@ Run the pre-rebase hook
**Choices:** `human`, `json`, `jsonl`
-- **`--from-ref `** — Start reference for checking files (requires --to-ref)
+- **`--from-ref `** — Select files changed since this reference; optionally pair with --to-ref
- **`--no-fail-fast`** — Continue on failures (opposite of --fail-fast)
- **`--no-stage`** — Disable auto-staging of fixed files
- **`--pr`** — Check only files changed in the current PR/branch (shortcut for --from-ref DEFAULT_BRANCH --to-ref HEAD)
@@ -43,6 +48,12 @@ Run the pre-rebase hook
**Choices:** `git`, `patch-file`, `none`
- **`--stats`** — Display statistics about files matching each step
-- **`--to-ref `** — End reference for checking files (requires --from-ref)
+- **`--to-ref `** — End reference for comparison with --from-ref
- **`--unstaged`** — Run on unstaged and untracked files only (excludes staged files), without stashing. Useful for linting files an agent just changed.
- **`-h --help`** — Print help
+
+
+
+## Learn more
+
+[Git hooks and stashing](/hooks) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/run/prepare-commit-msg.md b/docs/cli/run/prepare-commit-msg.md
index f8d12e3f1..e1a61257d 100644
--- a/docs/cli/run/prepare-commit-msg.md
+++ b/docs/cli/run/prepare-commit-msg.md
@@ -1,3 +1,8 @@
+---
+title: "hk run prepare-commit-msg"
+description: "Arguments and options for hk run prepare-commit-msg."
+---
+
# `hk run prepare-commit-msg`
@@ -16,10 +21,10 @@ Run the prepare-commit-msg hook
## Flags
-- **`-a --all`** — Run on all files instead of just staged files
+- **`-a --all`** — Select all tracked and eligible untracked files, then apply step filters
- **`-c --check`** — Run check command instead of fix command
- **`-e --exclude `** — Exclude files that otherwise would have been selected
-- **`-f --fix`** — Run fix command instead of check command (this is the default behavior unless HK_FIX=0)
+- **`-f --fix`** — Run fix commands instead of check commands for this invocation
- **`-g --glob `** — Run on files that match these glob patterns
- **`-J --json`** — Output the plan as JSON when combined with --plan or --why
- **`-P --plan`** — Print the plan instead of running the hook
@@ -31,7 +36,7 @@ Run the prepare-commit-msg hook
**Choices:** `human`, `json`, `jsonl`
-- **`--from-ref `** — Start reference for checking files (requires --to-ref)
+- **`--from-ref `** — Select files changed since this reference; optionally pair with --to-ref
- **`--no-fail-fast`** — Continue on failures (opposite of --fail-fast)
- **`--no-stage`** — Disable auto-staging of fixed files
- **`--pr`** — Check only files changed in the current PR/branch (shortcut for --from-ref DEFAULT_BRANCH --to-ref HEAD)
@@ -45,6 +50,12 @@ Run the prepare-commit-msg hook
**Choices:** `git`, `patch-file`, `none`
- **`--stats`** — Display statistics about files matching each step
-- **`--to-ref `** — End reference for checking files (requires --from-ref)
+- **`--to-ref `** — End reference for comparison with --from-ref
- **`--unstaged`** — Run on unstaged and untracked files only (excludes staged files), without stashing. Useful for linting files an agent just changed.
- **`-h --help`** — Print help
+
+
+
+## Learn more
+
+[Git hooks and stashing](/hooks) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/sponsors.md b/docs/cli/sponsors.md
index 7926316cb..6c56933cd 100644
--- a/docs/cli/sponsors.md
+++ b/docs/cli/sponsors.md
@@ -1,3 +1,8 @@
+---
+title: "hk sponsors"
+description: "List the sponsors supporting hk and related open source tools."
+---
+
# `hk sponsors`
@@ -10,3 +15,17 @@ Show the companies sponsoring hk and the jdx.dev open source tools
## Flags
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+List the sponsors supporting hk and related open source tools.
+
+```sh
+hk sponsors
+```
+
+## Learn more
+
+[Getting started](/getting_started) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/test.md b/docs/cli/test.md
index 6407e92db..38fc4631d 100644
--- a/docs/cli/test.md
+++ b/docs/cli/test.md
@@ -1,3 +1,8 @@
+---
+title: "hk test"
+description: "List step-defined tests, or run only the tests belonging to one configured step."
+---
+
# `hk test`
@@ -12,3 +17,19 @@ Run step-defined tests
- **`--name …`** — Filter by test name (repeatable)
- **`--step …`** — Filter by step name (repeatable)
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+List step-defined tests, or run only the tests belonging to one configured step.
+
+```sh
+hk test --list
+hk test --step whitespace
+hk test --name 'accepts clean text'
+```
+
+## Learn more
+
+[Getting started](/getting_started) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/uninstall.md b/docs/cli/uninstall.md
index c07ba1a3e..371cb4c59 100644
--- a/docs/cli/uninstall.md
+++ b/docs/cli/uninstall.md
@@ -1,3 +1,8 @@
+---
+title: "hk uninstall"
+description: "Remove the installation in the matching scope; this does not delete hk.pkl."
+---
+
# `hk uninstall`
@@ -13,3 +18,18 @@ Removes hk's hooks from the current git repository, clearing both legacy `.git/h
- **`--global`** — Remove hk hooks from the user's global git config (`~/.gitconfig`).
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Remove the installation in the matching scope; this does not delete hk.pkl.
+
+```sh
+hk uninstall
+hk uninstall --global
+```
+
+## Learn more
+
+[Getting started](/getting_started) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/util.md b/docs/cli/util.md
index ef8c1f510..119170f9a 100644
--- a/docs/cli/util.md
+++ b/docs/cli/util.md
@@ -1,3 +1,8 @@
+---
+title: "hk util"
+description: "Utilities run directly on the arguments you provide. Several also have ready-to-use Builtins definitions."
+---
+
# `hk util`
@@ -29,3 +34,18 @@ Utility commands for file operations
- [`hk util python-check-ast …`](/cli/util/python-check-ast.md)
- [`hk util python-debug-statements …`](/cli/util/python-debug-statements.md)
- [`hk util trailing-whitespace [-d --diff] [-f --fix] …`](/cli/util/trailing-whitespace.md)
+
+
+
+## Examples
+
+Utilities run directly on the arguments you provide. Several also have ready-to-use Builtins definitions.
+
+```sh
+hk util trailing-whitespace README.md
+hk util end-of-file-fixer README.md
+```
+
+## Learn more
+
+[Built-in linters and utilities](/builtins) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/util/check-added-large-files.md b/docs/cli/util/check-added-large-files.md
index 363595ac1..1af7433f4 100644
--- a/docs/cli/util/check-added-large-files.md
+++ b/docs/cli/util/check-added-large-files.md
@@ -1,3 +1,8 @@
+---
+title: "hk util check-added-large-files"
+description: "Check files against a size limit in kilobytes."
+---
+
# `hk util check-added-large-files`
@@ -18,3 +23,17 @@ Check for large files being added to the repository
**Default:** `500`
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Check files against a size limit in kilobytes.
+
+```sh
+hk util check-added-large-files --maxkb 1024 assets/logo.png
+```
+
+## Learn more
+
+[Built-in linters and utilities](/builtins) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/util/check-byte-order-marker.md b/docs/cli/util/check-byte-order-marker.md
index 61bc37b90..c03bc10ab 100644
--- a/docs/cli/util/check-byte-order-marker.md
+++ b/docs/cli/util/check-byte-order-marker.md
@@ -1,3 +1,8 @@
+---
+title: "hk util check-byte-order-marker"
+description: "Check for a byte order marker without changing the file."
+---
+
# `hk util check-byte-order-marker`
@@ -15,3 +20,17 @@ Check for UTF-8 byte order marker (BOM)
- **`-d --diff`** — Output a diff of the BOM removal instead of the file name
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Check for a byte order marker without changing the file.
+
+```sh
+hk util check-byte-order-marker README.md
+```
+
+## Learn more
+
+[Built-in linters and utilities](/builtins) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/util/check-case-conflict.md b/docs/cli/util/check-case-conflict.md
index e74c4c8f1..c4d749466 100644
--- a/docs/cli/util/check-case-conflict.md
+++ b/docs/cli/util/check-case-conflict.md
@@ -1,3 +1,8 @@
+---
+title: "hk util check-case-conflict"
+description: "Compare the supplied paths for case-insensitive naming collisions."
+---
+
# `hk util check-case-conflict`
@@ -14,3 +19,17 @@ Check for case-insensitive filename conflicts
## Flags
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Compare the supplied paths for case-insensitive naming collisions.
+
+```sh
+hk util check-case-conflict src/App.ts src/app.ts
+```
+
+## Learn more
+
+[Built-in linters and utilities](/builtins) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/util/check-conventional-commit.md b/docs/cli/util/check-conventional-commit.md
index ab87c9013..21ad7396e 100644
--- a/docs/cli/util/check-conventional-commit.md
+++ b/docs/cli/util/check-conventional-commit.md
@@ -1,3 +1,8 @@
+---
+title: "hk util check-conventional-commit"
+description: "Validate a file containing a commit message."
+---
+
# `hk util check-conventional-commit`
@@ -20,3 +25,17 @@ Titles starting with `fixup! `, `squash! `, or `amend! ` (temporary commits crea
**Default:** `build,chore,ci,docs,feat,fix,perf,refactor,revert,style,test`
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Validate a file containing a commit message.
+
+```sh
+hk util check-conventional-commit .git/COMMIT_EDITMSG
+```
+
+## Learn more
+
+[Built-in linters and utilities](/builtins) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/util/check-executables-have-shebangs.md b/docs/cli/util/check-executables-have-shebangs.md
index ed1c754e1..5e6f62421 100644
--- a/docs/cli/util/check-executables-have-shebangs.md
+++ b/docs/cli/util/check-executables-have-shebangs.md
@@ -1,3 +1,8 @@
+---
+title: "hk util check-executables-have-shebangs"
+description: "Check an executable script's shebang."
+---
+
# `hk util check-executables-have-shebangs`
@@ -14,3 +19,17 @@ Check that executable files have shebangs
## Flags
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Check an executable script's shebang.
+
+```sh
+hk util check-executables-have-shebangs scripts/check.sh
+```
+
+## Learn more
+
+[Built-in linters and utilities](/builtins) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/util/check-merge-conflict.md b/docs/cli/util/check-merge-conflict.md
index 37c72abde..20136d7db 100644
--- a/docs/cli/util/check-merge-conflict.md
+++ b/docs/cli/util/check-merge-conflict.md
@@ -1,3 +1,8 @@
+---
+title: "hk util check-merge-conflict"
+description: "Scan for merge markers even when Git is not currently merging."
+---
+
# `hk util check-merge-conflict`
@@ -15,3 +20,17 @@ Check for merge conflict markers
- **`--assume-in-merge`** — Run the check even when not in a merge
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Scan for merge markers even when Git is not currently merging.
+
+```sh
+hk util check-merge-conflict --assume-in-merge src/main.rs
+```
+
+## Learn more
+
+[Built-in linters and utilities](/builtins) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/util/check-symlinks.md b/docs/cli/util/check-symlinks.md
index ba0889e46..fba2d1914 100644
--- a/docs/cli/util/check-symlinks.md
+++ b/docs/cli/util/check-symlinks.md
@@ -1,3 +1,8 @@
+---
+title: "hk util check-symlinks"
+description: "Check the supplied symbolic link targets."
+---
+
# `hk util check-symlinks`
@@ -14,3 +19,17 @@ Check for broken symlinks
## Flags
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Check the supplied symbolic link targets.
+
+```sh
+hk util check-symlinks bin/tool
+```
+
+## Learn more
+
+[Built-in linters and utilities](/builtins) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/util/detect-private-key.md b/docs/cli/util/detect-private-key.md
index bfde2321a..79e299d00 100644
--- a/docs/cli/util/detect-private-key.md
+++ b/docs/cli/util/detect-private-key.md
@@ -1,3 +1,8 @@
+---
+title: "hk util detect-private-key"
+description: "Check the supplied files for recognized private-key markers."
+---
+
# `hk util detect-private-key`
@@ -14,3 +19,17 @@ Detect private keys in files
## Flags
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Check the supplied files for recognized private-key markers.
+
+```sh
+hk util detect-private-key config/example.env
+```
+
+## Learn more
+
+[Built-in linters and utilities](/builtins) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/util/end-of-file-fixer.md b/docs/cli/util/end-of-file-fixer.md
index 013ef42e5..28c8fad38 100644
--- a/docs/cli/util/end-of-file-fixer.md
+++ b/docs/cli/util/end-of-file-fixer.md
@@ -1,3 +1,8 @@
+---
+title: "hk util end-of-file-fixer"
+description: "Check first, or request an in-place fix explicitly."
+---
+
# `hk util end-of-file-fixer`
@@ -16,3 +21,18 @@ Check for and optionally fix missing final newlines
- **`-d --diff`** — Output a diff of the change. Cannot use with `fix`.
- **`-f --fix`** — Fix files to end with exactly one newline
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Check first, or request an in-place fix explicitly.
+
+```sh
+hk util end-of-file-fixer README.md
+hk util end-of-file-fixer --fix README.md
+```
+
+## Learn more
+
+[Built-in linters and utilities](/builtins) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/util/fix-byte-order-marker.md b/docs/cli/util/fix-byte-order-marker.md
index 720019b4b..8521ca8be 100644
--- a/docs/cli/util/fix-byte-order-marker.md
+++ b/docs/cli/util/fix-byte-order-marker.md
@@ -1,3 +1,8 @@
+---
+title: "hk util fix-byte-order-marker"
+description: "This command modifies the supplied files to remove a UTF-8 BOM."
+---
+
# `hk util fix-byte-order-marker`
@@ -14,3 +19,17 @@ Remove UTF-8 byte order marker (BOM)
## Flags
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+This command modifies the supplied files to remove a UTF-8 BOM.
+
+```sh
+hk util fix-byte-order-marker README.md
+```
+
+## Learn more
+
+[Built-in linters and utilities](/builtins) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/util/fix-smart-quotes.md b/docs/cli/util/fix-smart-quotes.md
index 5bddcfa6b..df994cf6b 100644
--- a/docs/cli/util/fix-smart-quotes.md
+++ b/docs/cli/util/fix-smart-quotes.md
@@ -1,3 +1,8 @@
+---
+title: "hk util fix-smart-quotes"
+description: "This utility fixes by default; use --check to inspect without editing."
+---
+
# `hk util fix-smart-quotes`
@@ -16,3 +21,18 @@ Replace smart quotes with plain ASCII quotes
- **`--check`** — Check for smart quotes without fixing them
- **`-d --diff`** — Output a diff of the change (implies `--check`)
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+This utility fixes by default; use --check to inspect without editing.
+
+```sh
+hk util fix-smart-quotes --check README.md
+hk util fix-smart-quotes README.md
+```
+
+## Learn more
+
+[Built-in linters and utilities](/builtins) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/util/mixed-line-ending.md b/docs/cli/util/mixed-line-ending.md
index 40901841a..ab4717211 100644
--- a/docs/cli/util/mixed-line-ending.md
+++ b/docs/cli/util/mixed-line-ending.md
@@ -1,3 +1,8 @@
+---
+title: "hk util mixed-line-ending"
+description: "Check for mixed line endings, or normalize them to LF."
+---
+
# `hk util mixed-line-ending`
@@ -16,3 +21,18 @@ Detect and fix mixed line endings
- **`-d --diff`** — Output a diff of the change. Cannot use with `fix`.
- **`-f --fix`** — Fix mixed line endings by normalizing to LF
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Check for mixed line endings, or normalize them to LF.
+
+```sh
+hk util mixed-line-ending README.md
+hk util mixed-line-ending --fix README.md
+```
+
+## Learn more
+
+[Built-in linters and utilities](/builtins) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/util/no-commit-to-branch.md b/docs/cli/util/no-commit-to-branch.md
index 8c725815b..d14162e1c 100644
--- a/docs/cli/util/no-commit-to-branch.md
+++ b/docs/cli/util/no-commit-to-branch.md
@@ -1,3 +1,8 @@
+---
+title: "hk util no-commit-to-branch"
+description: "Protect a named branch instead of the default main/master list."
+---
+
# `hk util no-commit-to-branch`
@@ -11,3 +16,17 @@ Prevent commits to specific branches
- **`--branch `** — Branch names to protect (default: main, master)
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Protect a named branch instead of the default main/master list.
+
+```sh
+hk util no-commit-to-branch --branch production
+```
+
+## Learn more
+
+[Built-in linters and utilities](/builtins) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/util/python-check-ast.md b/docs/cli/util/python-check-ast.md
index 474e5fd78..1530b681b 100644
--- a/docs/cli/util/python-check-ast.md
+++ b/docs/cli/util/python-check-ast.md
@@ -1,3 +1,8 @@
+---
+title: "hk util python-check-ast"
+description: "Validate Python syntax using python3 (or python). If neither interpreter is available, the utility skips the files. Python may create __pycache__ files."
+---
+
# `hk util python-check-ast`
@@ -14,3 +19,17 @@ Check Python files for valid syntax
## Flags
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Validate Python syntax using python3 (or python). If neither interpreter is available, the utility skips the files. Python may create **pycache** files.
+
+```sh
+hk util python-check-ast app.py
+```
+
+## Learn more
+
+[Built-in linters and utilities](/builtins) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/util/python-debug-statements.md b/docs/cli/util/python-debug-statements.md
index 26a3e7d3e..de139375f 100644
--- a/docs/cli/util/python-debug-statements.md
+++ b/docs/cli/util/python-debug-statements.md
@@ -1,3 +1,8 @@
+---
+title: "hk util python-debug-statements"
+description: "Check Python files for debugging statements."
+---
+
# `hk util python-debug-statements`
@@ -14,3 +19,17 @@ Detect Python debug statements
## Flags
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Check Python files for debugging statements.
+
+```sh
+hk util python-debug-statements app.py
+```
+
+## Learn more
+
+[Built-in linters and utilities](/builtins) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/util/trailing-whitespace.md b/docs/cli/util/trailing-whitespace.md
index 5b971e3d3..0a8f51c07 100644
--- a/docs/cli/util/trailing-whitespace.md
+++ b/docs/cli/util/trailing-whitespace.md
@@ -1,3 +1,8 @@
+---
+title: "hk util trailing-whitespace"
+description: "Check first, or remove trailing whitespace in place."
+---
+
# `hk util trailing-whitespace`
@@ -16,3 +21,18 @@ Check for and optionally fix trailing whitespace
- **`-d --diff`** — Output a diff of the change. Cannot use with `fix`.
- **`-f --fix`** — Fix trailing whitespace by removing it
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Check first, or remove trailing whitespace in place.
+
+```sh
+hk util trailing-whitespace README.md
+hk util trailing-whitespace --fix README.md
+```
+
+## Learn more
+
+[Built-in linters and utilities](/builtins) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/validate.md b/docs/cli/validate.md
index 41929b425..76ca958c2 100644
--- a/docs/cli/validate.md
+++ b/docs/cli/validate.md
@@ -1,3 +1,8 @@
+---
+title: "hk validate"
+description: "Evaluate the selected configuration without running its linter commands."
+---
+
# `hk validate`
@@ -10,3 +15,18 @@ Validate the config file
## Flags
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Evaluate the selected configuration without running its linter commands.
+
+```sh
+hk validate
+HK_FILE=./hk.local.pkl hk validate
+```
+
+## Learn more
+
+[Getting started](/getting_started) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/cli/version.md b/docs/cli/version.md
index c85db14ac..f7f7d6528 100644
--- a/docs/cli/version.md
+++ b/docs/cli/version.md
@@ -1,3 +1,8 @@
+---
+title: "hk version"
+description: "Print the installed version for diagnostics or a bug report."
+---
+
# `hk version`
@@ -10,3 +15,17 @@ Print the version of hk
## Flags
- **`-h --help`** — Print help
+
+
+
+## Examples
+
+Print the installed version for diagnostics or a bug report.
+
+```sh
+hk version
+```
+
+## Learn more
+
+[Getting started](/getting_started) · [Troubleshooting](/logging) · [All commands](/cli/)
diff --git a/docs/configuration.md b/docs/configuration.md
index 4d569248a..616f0900f 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -1,149 +1,107 @@
---
-outline: "deep"
+outline: deep
+description: Configure hooks, steps, file selection, profiles, local overrides, and runtime settings.
---
# Configuration
-hk builds its effective configuration by layering sources from lowest to highest precedence:
+hk reads `hk.pkl` to decide which steps to run and how to run them. Start with a shared set of linters, then add file filters, dependencies, and profiles as your project needs them.
-| Precedence | Source | Scope |
-| ----------- | ------------------------------------------------------ | ------------------------- |
-| 1 (lowest) | Built-in defaults | All projects |
-| 2 | [hkrc](#hkrc) (`~/.config/hk/config.pkl`) | All projects (user-level) |
-| 3 | [Project config](#hk-pkl) (`hk.pkl` or `hk.local.pkl`) | Single project |
-| 4 | [Git config](#git-configuration) (global, then local) | Per-repo |
-| 5 | [Environment variables](#settings-reference) (`HK_*`) | Per-invocation |
-| 6 (highest) | [CLI flags](#settings-reference) | Per-invocation |
-
-Higher layers override lower. For hooks and steps, layers are **additive** — hkrc can define hooks the project doesn't have, but the project's definition wins on collision. See the [hkrc](#hkrc) section for merge semantics.
+For a first setup, use [getting started](/getting_started). For complete configurations, see the [examples](/reference/examples/).
## `hk.pkl`
-hk is configured via `hk.pkl`, which is written in [Pkl](https://pkl-lang.org/) from Apple. By default, hk uses the built-in pklr evaluator, so the pkl CLI is not required. Set `HK_PKL_BACKEND=pkl` to use the pkl CLI instead.
-
-### Config File Paths
-
-hk searches for config files in the following order (first match wins):
-
-| Precedence | Path | Purpose |
-| ---------- | ---------------------- | ---------------------------------------------------------- |
-| 1 | `hk.local.pkl` | Local overrides, should not be committed to source control |
-| 2 | `.config/hk.local.pkl` | Local overrides, nested under `.config/` |
-| 3 | `hk.pkl` | Standard project config |
-| 4 | `.config/hk.pkl` | Standard project config, nested under `.config/` |
-
-hk walks up from the current directory to `/`, checking each directory for these files. The first file found is used.
-
-Set [`HK_FILE`](/environment_variables#hk-file) to override the search and use a specific path.
-
-> [!NOTE]
-> Unlike mise, hk does not merge multiple config files or support `conf.d/` directories. Local overrides use Pkl's `amends` mechanism instead (see [`hk.local.pkl`](#hk-local-pkl)).
-
-### Example
-
-Here's a basic `hk.pkl` file:
+A configuration amends hk’s [Pkl schema](/pkl_introduction) and defines named hooks:
```pkl
amends "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Config.pkl"
import "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Builtins.pkl"
local linters = new Mapping {
- // steps can be manually defined
- ["eslint"] {
- // the files to run the linter on, if no files are matched, the linter will be skipped
- // this will filter the staged files and return the subset matching these globs
- glob = List("*.js", "*.ts")
- // the command to run that makes no changes
- check = "eslint {{files}}"
- // the command to run that fixes the files (used by default)
- fix = "eslint --fix {{files}}"
- // optional: files matching these globs will be staged after fix modifies them
- // defaults to the step's glob when staging is enabled, so usually not needed
- // stage = List("*.js", "*.ts")
- }
- // steps can also be pulled from the Builtins pkl library
- ["prettier"] = Builtins.prettier
+ ["eslint"] = Builtins.eslint
+ ["prettier"] = Builtins.prettier
}
hooks {
- ["pre-commit"] {
- fix = true // runs the fix step to make modifications
- stash = "git" // stashes unstaged changes before running fix steps
- steps = linters
- }
- ["pre-push"] {
- steps = linters
- }
- // "fix" and "check" are special steps for `hk fix` and `hk check` commands
- ["fix"] {
- fix = true
- steps = linters
- }
- ["check"] {
- steps = linters
- // optional: run a report after the hook finishes; HK_REPORT_JSON contains timing JSON
- report = #"node scripts/upload-timings.js <<<"$HK_REPORT_JSON""#
- }
+ ["pre-commit"] {
+ fix = true
+ stash = "git"
+ steps = linters
+ }
+ ["check"] { steps = linters }
+ ["fix"] {
+ fix = true
+ steps = linters
+ }
}
```
-The first line (`amends`) is required: it imports the base configuration schema that `hk.pkl` extends.
-
-### Subprojects
-
-In a monorepo, the root config can load an `hk.pkl` owned by each component:
-
-```pkl
-subprojects = List("frontend", "backend", "packages/*")
-```
+`pre-commit` applies fixes to staged files while unstaged work is saved. The `check` and `fix` hooks provide the local commands. They are hooks, not individual steps.
-Subproject paths are relative to the root config and may be literal directories or
-glob patterns. hk merges a subproject's steps into the root hook with the same name,
-then scopes their working directories and file matching to that subproject. A step
-named `eslint` in `frontend/hk.pkl` is exposed as `frontend:eslint` for `--step` and
-`skip_steps`.
+### Config file paths
-Keep these composition rules in mind:
+Starting in the current directory, hk walks upward. At each directory it checks these paths in order, using the first match:
-- Hooks are not copied between events. A subproject step under `check` does not also
- run in `pre-commit` or `fix`; add it to every event where it should run.
-- Hook-wide behavior such as `fix`, `stash`, `stage`, and `report` should be set in
- the root config. Subprojects contribute steps and their local environment.
-- Subprojects are loaded one level deep. A `subprojects` declaration inside a
- subproject config is ignored with a warning.
-- A subproject's literal `dir` is relative to that subproject. Templated workspace
- directories have an additional caveat described under
- [Step working directory](#step-working-directory).
+| Order | Path | Purpose |
+| ----- | ---------------------- | ------------------------------------- |
+| 1 | `hk.local.pkl` | Local project override |
+| 2 | `.config/hk.local.pkl` | Local override under `.config/` |
+| 3 | `hk.pkl` | Shared project configuration |
+| 4 | `.config/hk.pkl` | Shared configuration under `.config/` |
-See the complete [monorepo example](/reference/examples/monorepo#nested-configs-with-subprojects),
-including per-directory mise environments and locally installed Node tools.
+[`HK_FILE`](/environment_variables#hk-file) selects a specific configuration instead. hk selects one project file; it does not merge every file it finds.
### `hk.local.pkl`
-If `hk.local.pkl` exists, it is used instead of `hk.pkl`. It is intended for local config and should
-not be committed to source control.
+Use Pkl’s `amends` to extend the shared project configuration locally:
-Its first line is expected to be `amends "./hk.pkl"` so that it extends the project config.
+```pkl
+amends "./hk.pkl"
+
+hooks {
+ ["check"] {
+ steps {
+ ["local-check"] {
+ check = "make local-check"
+ }
+ }
+ }
+}
+```
-Example:
+Add `hk.local.pkl` to `.git/info/exclude` or the project’s `.gitignore`. This example preserves inherited steps and adds one. Assign a new mapping when you want to replace the step list:
```pkl
amends "./hk.pkl"
-import "./hk.pkl" as repo_config
-
-hooks = (repo_config.hooks) {
- ["pre-commit"] {
- (steps) {
- ["custom-step"] = new Step {
- // ...
- }
- }
+
+hooks {
+ ["check"] {
+ steps = new Mapping {
+ ["local-check"] { check = "make local-check" }
}
+ }
}
+```
+
+## Define a step
+A step selects files and declares commands:
+
+```pkl
+local eslint = new Step {
+ glob = List("*.js", "*.ts")
+ exclude = List("**/generated/**")
+ check = "eslint {{files}}"
+ fix = "eslint --fix {{files}}"
+}
```
-
+- `glob` filters the files selected for the run. With no match, the step is skipped.
+- `check` should return a nonzero status for problems and leave files unchanged.
+- `fix` should apply available fixes and report any problems that remain.
+- `{{files}}` expands to the selected file arguments.
+
+A step without file patterns can run even when no files are selected. Use that for whole-project commands, and declare ordering when they read or write beyond a known file set.
### Step commands
@@ -228,301 +186,211 @@ This behavior is opt-in because it adds another process invocation and requires
For partial fixers, set `check_after_diff = true` alongside `check` and `check_diff`. After applying a nonempty diff in fix mode, hk reruns `check` on the original batch so non-fixable findings are not hidden by a successfully applied patch. Complete formatters can leave this disabled to retain the single-command fast path.
-### ``
+### Customize a builtin
-A group is a collection of steps that run in parallel with each other. The group waits for previous steps and groups to finish, and blocks later steps and groups from starting until it finishes. This is a coarse way to enforce execution order; read/write locks and `depends` are usually better.
+```pkl
+["prettier"] = (Builtins.prettier) {
+ glob = List("*.js", "*.ts", "*.json")
+ exclude = List("**/generated/**")
+}
+```
+
+The amended object keeps properties you do not override. See [builtins](/builtins) for the catalogue and command details.
-Steps should not normally run `git add` or `git update-index` themselves. Declare generated or modified files with the step's `stage` setting and let hk stage them; hk serializes its own index writes. If a legacy or third-party command stages files internally and cannot be changed, hk cannot infer that hidden write, so serialize it with `exclusive = true`, a `depends` chain, or a separate group.
+### Dependencies and groups
+
+Use `depends` when the result of one step is needed by another:
```pkl
-hooks {
- ["pre-commit"] {
- steps {
- ["build"] = new Group {
- steps = new Mapping {
- ["ts"] = new Step {
- fix = "tsc -b"
- }
- ["rs"] = new Step {
- fix = "cargo build"
- }
- }
- }
- // these steps will run in parallel after the build group finishes
- ["lint"] = new Group {
- steps = new Mapping {
- ["prettier"] = new Step {
- check = "prettier --check {{files}}"
- }
- ["eslint"] = new Step {
- check = "eslint {{files}}"
- }
- }
- }
- }
- }
+["prettier"] = (Builtins.prettier) {
+ depends = "eslint"
}
```
-Groups may define a small set of step settings that child steps inherit when they do not define their own value:
+This waits for the `eslint` step. File locking already prevents simultaneous writes to selected files; a dependency additionally establishes their order.
+
+Prefer the step’s `stage` setting over running `git add` inside a command; hk serializes its own index writes. Serialize commands that write the index themselves with `exclusive`, `depends`, or a group.
-| Group option | Inherited step option | Type |
-| --------------------- | --------------------- | ------------------------------------ |
-| `dir` | `dir` | `String?` |
-| `prefix` | `prefix` | `(String \| List)?` |
-| `workspace_indicator` | `workspace_indicator` | `String?` |
-| `shell` | `shell` | `(String \| Script)?` |
-| `stage` | `stage` | `(String \| List)?` |
-| `exclude` | `exclude` | `(String \| List \| Regex)?` |
+A `Group` is a scheduling boundary. Its child steps can run together, but the group waits for prior work and blocks later work until it finishes. Prefer individual dependencies when only a few steps need an order.
-Inheritance uses simple override semantics. If a child step defines the field, the child value is used. Otherwise, the group value is copied to the step. Values are not merged.
+#### Group defaults {#group}
```pkl
local frontend = new Group {
- dir = "packages/frontend"
- prefix = "mise x --"
- steps {
- ["prettier"] = (Builtins.prettier) {
- batch = true
- }
- ["eslint"] = (Builtins.eslint) {
- dir = "different/path"
- batch = true
- }
- }
+ dir = "frontend"
+ prefix = List("mise", "x", "--")
+ steps {
+ ["prettier"] = Builtins.prettier
+ ["eslint"] = Builtins.eslint
+ }
}
```
-In this example, `prettier` inherits `dir = "packages/frontend"` and `prefix = "mise x --"`. `eslint` keeps its explicit `dir = "different/path"` and still inherits `prefix = "mise x --"`.
+Groups can provide `dir`, `prefix`, `workspace_indicator`, `shell`, `stage`, and `exclude`. A child inherits a value only when it does not define its own. Child values replace group values; lists are not merged. A builtin may already define a property, so inspect its definition before relying on inheritance.
-String prefixes are shell syntax and can only be used with string commands. For a
-structured command, use a list so each prefix argument retains its boundary:
+### Profiles
+
+Profiles select optional steps:
```pkl
-["ruff"] = (Builtins.ruff) {
- prefix = List("mise", "x", "--")
+["typecheck"] = (Builtins.tsc) {
+ profiles = List("slow")
}
```
-An argv prefix is rendered and prepended to the structured command without invoking
-a shell. It cannot be combined with a string command, and it cannot contain
-`{{files}}` or `{{workspace_files}}`.
+```sh
+hk check --slow
+hk check --profile slow
+HK_PROFILE=slow hk check
+```
-## Git status in conditions and templates
+A step requires **all** of its positive profile names to be enabled. `profiles = List("ci", "slow")` requires both `ci` and `slow`. A negative profile such as `"!slow"` prevents that step from running when `slow` is enabled. Quote `!slow` when passing it through a shell.
-hk provides the current git status to both condition expressions and Tera templates via a `git` object. This lets you avoid shelling out in conditions (e.g. `exec('git …')`).
+Set active profiles at the top level, via CLI flags, Git config, or `HK_PROFILE`. A hook’s `env` block configures child commands; it is not the place to select hk’s profiles.
-- Available fields: `git.staged_files`, `git.unstaged_files`, `git.untracked_files`, `git.modified_files`
- - Staged classifications: `git.staged_added_files`, `git.staged_modified_files`, `git.staged_deleted_files`, `git.staged_renamed_files`, `git.staged_copied_files`
- - Unstaged classifications: `git.unstaged_modified_files`, `git.unstaged_deleted_files`, `git.unstaged_renamed_files`
+### Workspaces
-- In conditions (expr):
+Use `workspace_indicator` for a tool that works on a project identified by a file:
```pkl
-// Run only if there are any staged files
-condition = "git.staged_files != []"
+["cargo-clippy"] = (Builtins.cargo_clippy) {
+ workspace_indicator = "Cargo.toml"
+ check = "cargo clippy --manifest-path {{workspace_indicator}}"
+}
+```
-// Run only if a Cargo.toml file is staged
-condition = #"any(git.staged_files, {hasSuffix(#, "Cargo.toml")})"#
+hk partitions selected files by the matching workspace. `{{workspace}}` is its directory, `{{workspace_indicator}}` is the marker’s path, and `{{workspace_files}}` contains paths relative to that directory.
-// Diff-filter approximations
-// Added or Renamed (AR):
-condition = "(git.staged_added_files != []) || (git.staged_renamed_files != [])"
+See the [monorepo example](/reference/examples/monorepo) for component groups and working directories.
-// Renamed or Deleted (RD):
-condition = "(git.staged_renamed_files != []) || (git.staged_deleted_files != [])"
-```
+### Subprojects
-- In templates (Tera):
+In a monorepo, the root config can load an `hk.pkl` owned by each component:
```pkl
-check = "echo staged: {{ git.staged_files }}"
+subprojects = List("frontend", "backend", "packages/*")
```
-These lists contain repository-relative paths for files currently in each state.
+Subproject paths are relative to the root config and may be literal directories or
+glob patterns. hk merges a subproject's steps into the root hook with the same name,
+then scopes their working directories and file matching to that subproject. A step
+named `eslint` in `frontend/hk.pkl` is exposed as `frontend:eslint` for `--step` and
+`skip_steps`.
-## `hkrc`
+Keep these composition rules in mind:
-> [!WARNING]
-> `.hkrc.pkl` and `--hkrc` are deprecated and will be removed in hk v2.
->
-> - **Per-project overrides:** use `hk.local.pkl` in the project root (see [`hk.local.pkl`](#hk-local-pkl))
-> - **Global user config:** use `~/.config/hk/config.pkl`
+- Hooks are not copied between events. A subproject step under `check` does not also
+ run in `pre-commit` or `fix`; add it to every event where it should run.
+- Hook-wide behavior such as `fix`, `stash`, `stage`, and `report` should be set in
+ the root config. Subprojects contribute steps and their local environment.
+- Subprojects are loaded one level deep. A `subprojects` declaration inside a
+ subproject config is ignored with a warning.
+- A subproject's literal `dir` is relative to that subproject. Templated workspace
+ directories have an additional caveat described under
+ [Step working directory](#step-working-directory).
-The `hkrc` is a user-level configuration file that customizes hk's behavior across all projects. hk discovers it in this order (first match wins):
+See the complete [monorepo example](/reference/examples/monorepo#nested-configs-with-subprojects),
+including per-directory mise environments and locally installed Node tools.
-| Precedence | Path | Purpose |
-| ---------- | ------------------------- | --------------------------------------- |
-| 1 | `.hkrc.pkl` (CWD) | Per-directory override **(deprecated)** |
-| 2 | `~/.hkrc.pkl` | Home directory **(deprecated)** |
-| 3 | `~/.config/hk/config.pkl` | XDG config directory **(recommended)** |
+### Conditions and Git status
-The `--hkrc` flag, which overrides discovery with a specific path, is deprecated and hidden from `hk --help`.
+`condition` is an expression evaluated per step job. `step_condition` is evaluated once per step. Shell commands need an explicit `exec(...)` call:
-The hkrc file follows the same format as `hk.pkl` and can define hooks and linters that apply to all projects. This is useful for setting up consistent linting rules across multiple repositories.
+```pkl
+condition = "exec('test -f .lint-enabled')"
+```
-Example hkrc file:
+The `git` object makes common status checks available without invoking Git:
```pkl
-amends "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Config.pkl"
-import "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Builtins.pkl"
+condition = "git.staged_files != []"
+```
-local linters {
- ["prettier"] = Builtins.prettier
- ["eslint"] {
- glob = List("*.js", "*.ts")
- check = "eslint {{files}}"
- fix = "eslint --fix {{files}}"
- }
-}
+To require a staged Cargo manifest:
-hooks {
- ["pre-commit"] {
- fix = true
- steps = linters
- }
-}
+```pkl
+condition = #"any(git.staged_files, {hasSuffix(#, "Cargo.toml")})"#
```
-An hkrc that amends `Config.pkl` is merged with the project configuration using "project wins" semantics:
+Available lists include `staged_files`, `unstaged_files`, `untracked_files`, and `modified_files`. Staged classifications include `staged_added_files`, `staged_modified_files`, `staged_deleted_files`, `staged_renamed_files`, and `staged_copied_files`. Unstaged classifications include `unstaged_modified_files`, `unstaged_deleted_files`, and `unstaged_renamed_files`.
+
+These paths are repository-relative. Git status lists are also available to command templates, for example `{{ git.staged_files }}`.
+
+## Configuration precedence
-- **Settings** (jobs, fail_fast, etc.): project config overrides hkrc values
-- **Environment variables**: hkrc values are set first; project config can override them
-- **Hooks/steps**: additive. hkrc can add hooks and steps the project doesn't define, but when both define the same step, the project's definition wins
+Runtime settings resolve from lowest to highest precedence:
-A legacy hkrc that amends `UserConfig.pkl` is the exception; see the note under [User Configuration](#user-configuration-config-hk-config-pkl).
+| Precedence | Source |
+| ---------- | -------------------------------------------------------------------- |
+| 1 | Built-in defaults |
+| 2 | User configuration, typically `~/.config/hk/config.pkl` |
+| 3 | Selected project configuration |
+| 4 | Git configuration, with local values overriding global/system values |
+| 5 | `HK_*` environment variables |
+| 6 | CLI flags |
-### How to manage global hook preferences
+Higher layers override lower ones for scalar settings. List settings such as `exclude`, `skip_steps`, `skip_hooks`, and `hide_warnings` combine values across sources.
-**Run your own linters on every project**
+### User configuration {#hkrc}
-Add steps to your hkrc. hk merges them into every project's hooks, so steps with names the project doesn't define always run:
+Use `~/.config/hk/config.pkl` for defaults and additional steps across projects. The location follows `XDG_CONFIG_HOME` or `HK_CONFIG_DIR` when set.
```pkl
-// ~/.config/hk/config.pkl
amends "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Config.pkl"
-hooks {
- ["pre-commit"] {
- steps {
- ["gitleaks"] { check = "gitleaks git --staged" }
- }
- }
-}
+jobs = 4
+fail_fast = false
+skip_steps = List("optional-check")
```
-**Skip steps you don't want from a project**
+For user files amending `Config.pkl`, hooks and steps merge additively with the project: user configuration adds names the project does not define, and project definitions win on collisions. Use `hk.local.pkl` to replace project behavior locally.
-hkrc can't remove project steps, because the project wins on collision. To skip a step, use git config in that repo (persists) or an environment variable (one run):
+::: warning Legacy user schema
+Files amending `UserConfig.pkl` use older precedence: their `display_skip_reasons`, `hide_warnings`, `warnings`, `stage`, and environment values override the project. Amend `Config.pkl` to use the precedence described above.
+:::
-```bash
-# Skip a step permanently in this repo
-git config --local hk.skipSteps "slow-linter,noisy-formatter"
+::: warning Legacy hkrc paths
+`.hkrc.pkl`, `~/.hkrc.pkl`, and `--hkrc` are deprecated. Discovery still checks the legacy files before the user configuration directory. Migrate project overrides to `hk.local.pkl` and shared user defaults to `~/.config/hk/config.pkl`.
+:::
-# Skip for one run
-HK_SKIP_STEPS=slow-linter hk run pre-commit
-```
+Global configuration is separate from [global hook installation](/getting_started#install-hooks). An installed hook in a repository without a project configuration exits silently.
-**Completely replace a project's hooks locally**
+### Git configuration
-Create `hk.local.pkl` in the project root (don't commit it). hk loads it instead of `hk.pkl`, so amend the project config and redefine only what you want:
+Use Git settings for persistent preferences without modifying `hk.pkl`:
-```pkl
-// hk.local.pkl (add to .gitignore)
-amends "./hk.pkl"
-import "./hk.pkl" as upstream
-
-hooks = (upstream.hooks) {
- ["pre-commit"] {
- steps {
- // keep only the steps you want
- ["gitleaks"] = upstream.hooks["pre-commit"].steps["gitleaks"]
- }
- }
-}
+```sh
+git config --local hk.jobs 4
+git config --local hk.skipSteps "slow-test,noisy-formatter"
+git config --local hk.skipHook pre-push
+git config --global hk.failFast false
```
-## Settings Reference
-
-This section lists the configuration settings that control how hk behaves. Settings are sourced from multiple places; higher precedence overrides lower. Some list settings (e.g. `exclude`, `skip_steps`, `skip_hooks`, `hide_warnings`) use union semantics, combining values from multiple sources.
-
-| Precedence | Source | Example |
-| ---------- | ------------------------------ | --------------------------------------- |
-| 1 | CLI flags | `hk check --fail-fast` |
-| 2 | Environment variables (HK\_\*) | `HK_JOBS=8 hk check` |
-| 3 | Git config (local repo) | `git config --local hk.jobs 4` |
-| 4 | Git config (global/system) | `git config --global hk.failFast false` |
-| 5 | Project config (hk.pkl) | `jobs = 4` in `hk.pkl` |
-| 6 | User rc (hkrc) | `jobs = 4` in `~/.config/hk/config.pkl` |
-| 7 | Built-in defaults | `jobs = 0` (auto, CPU cores) |
-
-### Git Configuration
-
-hk can be configured through git config. All git config keys use the `hk.` prefix:
-
-```bash
-# Set number of parallel jobs
-git config --local hk.jobs 5
+List settings accept comma-separated values or multiple Git entries:
-# Disable fail-fast behavior
-git config --local hk.failFast false
-
-# Add profiles
-git config --local hk.profile slow
-git config --local --add hk.profile fast
-
-# Add exclude patterns (union semantics)
-git config --local hk.exclude "node_modules"
+```sh
+git config --local hk.exclude node_modules
git config --local --add hk.exclude "**/*.min.js"
-
-# Skip specific steps
-git config --local hk.skipSteps "slow-test,flaky-test"
-
-# Skip entire hooks
-git config --local hk.skipHook "pre-push"
-
-# Configure warnings
-git config --local hk.warnings "missing-profiles"
-git config --local hk.hideWarnings "missing-profiles"
```
-Git config supports both multivar entries (multiple values with the same key) and comma-separated values in a single entry.
-
-### User Configuration (`~/.config/hk/config.pkl`)
-
-User-specific defaults can be set in `~/.config/hk/config.pkl`:
-
-```pkl
-amends "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Config.pkl"
+### Inspect effective settings
-jobs = 4
-fail_fast = false
-exclude = List("node_modules", "dist", "build")
-skip_steps = List("slow-test")
-skip_hooks = List("pre-push")
+```sh
+hk config dump
+hk config get exclude
+hk config explain jobs
```
-> [!NOTE]
-> Legacy hkrc files that amend `UserConfig.pkl` are still supported, but they do not
-> follow the "project wins" rule above. Values they set for `display_skip_reasons`,
-> `hide_warnings`, `warnings`, `stage`, and environment variables override the project
-> config instead. hk treats an hkrc as legacy when it exposes an `environment` key
-> rather than `env`. Amend `Config.pkl` to get the precedence documented above.
+These commands inspect runtime settings. To inspect hook execution, use `hk check --plan`; to evaluate the Pkl file, use `hk validate` or the optional Pkl CLI.
-### Configuration Introspection
+## Schema reference
-Use the `hk config` commands to inspect your configuration:
+The following reference is generated from the schema’s documentation. It covers top-level configuration, hooks, steps, and groups.
-```bash
-# Show effective configuration (all sources merged)
-hk config dump
+
-# Get a specific configuration value
-hk config get exclude
-hk config get skip_steps
+## Settings reference
-# Show configuration source precedence
-hk config sources
-```
+Each setting below lists its type, default, and supported sources. Pkl property names use underscores; CLI flags generally use hyphens.
diff --git a/docs/contributing.md b/docs/contributing.md
index a0beaeca4..6011e95b1 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -1,82 +1,93 @@
-# Contributing
-
-Thank you for your interest in contributing to hk! This guide will help you get started.
-
-## Contribution Expectations
+---
+description: Set up hk for development, run focused checks, edit generated documentation, and prepare a contribution for review.
+---
-Before opening a PR, unless it is something obvious, consider creating a
-discussion or mentioning what you plan to do in
-[Discord](https://discord.gg/UBa7pJUN7Z). The important part is to settle the
-direction before much review happens. hk has a specific scope and design
-taste. I am comfortable saying no to changes that do not clearly fit.
+# Contributing
-Before I review a PR, CI must be passing and all automated AI review comments
-must be addressed. If those are still open, assume I will wait to look at the
-PR.
+Bug fixes, documentation improvements, and builtin definitions are welcome. For a substantial feature or behavior change, discuss the direction before investing in implementation.
-If I am on the fence about a contribution, I will probably reject it for that
-reason alone. If I did not do this, hk would suffer from feature bloat. I
-may also reject a PR if the quality is poor enough that I do not have confidence
-the contributor can get it across the finish line. I do not have time to coach
-contributors.
+## Review expectations
-I get hundreds of PRs per week across my projects, so I do not have time to
-respond to every PR with detailed context. A rejection may be brief.
+Open a [discussion](https://github.com/jdx/hk/discussions) or ask in [Discord](https://discord.gg/UBa7pJUN7Z) before starting a change whose scope or design is not obvious. hk has a deliberate scope; the maintainer may decline features that add complexity or long-term maintenance without a clear fit.
-## Prerequisites
+Before requesting review, make sure CI passes and address automated review comments. Explain the problem, resulting behavior, and validation in the PR. Contributions should be complete enough to assess without extensive coaching.
-- [mise](https://mise.jdx.dev/)
-- [Rust](https://www.rust-lang.org/)
+The maintainer handles a high volume of contributions across projects. Feedback or rejection may be brief, and uncertain fit can be enough to decline a change.
-## Setup
+## Development setup
-1. Clone the repository:
+Install [mise](https://mise.jdx.dev/) and a Rust toolchain compatible with the repository’s `Cargo.toml`, then:
```sh
git clone https://github.com/jdx/hk.git
cd hk
+mise install
+mise run build
```
-2. Install required tools and dependencies:
+The build task generates the builtin registry before compiling hk. See the [build cache guidance](https://github.com/jdx/hk/blob/main/CONTRIBUTING.md#mbx-build-cache) if the Cargo wrapper needs diagnosis. Development tasks put the local debug binary on `PATH`.
-```sh
-mise install
-```
+## Run focused checks
-## Running Tests
+| Task | Command |
+| ---------------------- | ------------------------------------ |
+| Build | `mise run build` |
+| Rust tests | `mise run test:cargo` |
+| One Rust test | `cargo test test_name` |
+| Bats integration tests | `mise run test:bats` |
+| One Bats file | `mise run test:bats test/check.bats` |
+| Full test suite | `mise run test` |
+| Lint | `hk check --all` |
+| Lint including Clippy | `hk check --all --slow` |
+| Apply formatting fixes | `hk fix --all` |
-To run the test suite, use the following command:
+Run checks appropriate to the change. Integration tests use isolated temporary repositories and exercise Git backends. See the [test-suite guide](https://github.com/jdx/hk/blob/main/test/README.md) for fixtures and cache behavior.
-```sh
-mise run test
-```
+## Add a builtin
+
+1. Add `pkl/builtins/.pkl` with metadata, file patterns, and commands.
+2. Define Pkl-level tests in the step’s `tests` field. Use `TestMaker` from `pkl/builtins/test/helpers.pkl` for standard check/fix patterns.
+3. Add a `mise tool-stub` script in `test/builtin_tool_stubs/` if the tool is not already available.
+4. Regenerate and build with `mise run build`.
+5. Run `mise run test:bats test/builtins_tests.bats`, or use `hk test --step ` with a configuration that loads the builtin.
-This will run all tests, including Bats shell tests and any other checks defined in the project.
+Tests should verify meaningful behavior: a clean check, a failing check, and the expected result of a fix when supported. Avoid enabling batching or bypassing locks without confirming the tool’s behavior.
-To run a specific test, use the following command:
+## Edit documentation
+
+The website uses VitePress. Run these from the repository root:
```sh
-mise run test:bats -- test/workspace_indicator.bats
+mise run docs # Generate reference content and start the dev server
+mise run docs:build # Generate and build the production site
```
-## Code Style
+Use the source of truth for each type of page:
-Check/format code with hk:
+| Content | Edit here |
+| ---------------------- | ----------------------------------------------------------------------------- |
+| README and guides | `README.md`, `docs/*.md` |
+| Landing page and theme | `docs/.vitepress/theme/` |
+| Navigation | `docs/.vitepress/config.mts` |
+| Schema reference | Documentation comments in `pkl/Config.pkl` |
+| Settings reference | `docs` strings in `settings.toml` |
+| Builtin catalogue | Metadata and definitions in `pkl/builtins/` |
+| CLI reference | Rust CLI help and usage definitions; examples in `scripts/enrich-cli-docs.py` |
+| Downloadable examples | `docs/public/*.pkl`, included directly by their guide pages |
-```sh
-hk fix --all
-```
+`mise run docs:gen` generates `docs/gen/`. To regenerate the CLI reference, use `mise run render:usage`; that task also stages its generated outputs. Run `scripts/generate-examples.sh` to validate the downloadable examples and their documentation includes.
-Or with the mise task:
+Keep examples complete when they are intended to be copied. Label fragments, list external tool requirements, and use one package version for both schema and builtin imports. Verify internal links and inspect affected layouts at narrow and wide widths.
-```sh
-mise run lint-fix
-```
+## Commit messages and pull requests
+
+Use Conventional Commits with a lowercase, imperative description:
-## Commit and PR Titles
+- `fix(step): handle missing files`
+- `feat(builtins): add a formatter`
+- `docs: clarify hook installation`
+- `chore: update CI tooling`
-Use Conventional Commits for commit messages and PR titles. Examples:
+Use `fix` for changes to CLI behavior; use `chore` for CI and infrastructure. Add a command or subsystem scope when applicable.
-- `fix: handle missing config file`
-- `docs: clarify installation steps`
-- `feat: add quiet output mode`
+Open a PR ready for review with focused changes and a concise validation summary. Follow the repository’s [agent guidelines](https://github.com/jdx/hk/blob/main/AGENTS.md) when using coding agents.
diff --git a/docs/environment_variables.md b/docs/environment_variables.md
index 06c137847..43dc85018 100644
--- a/docs/environment_variables.md
+++ b/docs/environment_variables.md
@@ -1,226 +1,156 @@
---
-outline: "deep"
+outline: [2, 2]
+description: Configure hk with environment variables for files, profiles, execution, logging, Pkl evaluation, and stashing.
---
-# Environment Variables
+# Environment variables
-Environment variables can be used to configure hk.
+Use environment variables for a single invocation or an environment-wide preference. Runtime settings generally override Git and Pkl settings; CLI flags take precedence. See [configuration precedence](/configuration#configuration-precedence).
-Most of these map to settings that can also be configured via CLI flags, git config, `hk.pkl`, or user config. See the
-[Settings Reference](/configuration#settings-reference) for every setting's available sources and precedence. Variables are listed here in alphabetical order.
+Boolean settings accept `1`/`true` and `0`/`false`. List settings use commas. Set hk’s variables in the environment that launches hk; hook and step `env` blocks are for child commands.
-## `HK_CACHE`
+Common overrides:
-Type: `bool`
-Default: `true` (release builds), `false` (debug builds)
-
-Controls whether hk caches data such as parsed configuration files. Set to `0` or `false` to disable caching.
-
-## `HK_CACHE_DIR`
-
-Type: `path`
-Default: `~/.cache/hk`
-
-The cache directory to use.
-
-## `HK_CHECK`
-
-Type: `bool`
-Default: `false`
-
-Forces hooks to run their check commands instead of their fix commands, the opposite of
-`HK_FIX`. Useful in CI, where you want a report rather than fixes.
-
-By convention a check command only reports problems, but hk does not enforce that, so this
-variable selects which command runs rather than guaranteeing an unchanged worktree. A
-project whose check command edits files will still edit them.
-
-`--check` and `--fix` are per-invocation flags and outrank this variable, so
-`HK_CHECK=1 hk fix --fix` still runs fix commands. When both `HK_CHECK` and `HK_FIX` are
-enabled, `HK_CHECK` wins. `git config hk.check` sets the same value at a lower precedence.
-
-## `HK_CHECK_FIRST`
+```sh
+HK_PROFILE=slow hk check --all
+HK_SKIP_STEPS=eslint hk check
+HK_LOG=debug hk run pre-commit
+```
-Type: `bool`
-Default: `true`
+## `HK` {#hk}
-If `true`, when multiple steps match the same file, hk runs their check commands first and only runs a step's fix command if its check fails.
+**Type:** boolean · **Default:** enabled in installed launchers
-This lets hk parallelize as much as possible. Any number of check commands can run against the same file at once without interfering with each other, but two fix commands cannot safely write to the same file. So hk optimistically runs the check commands in parallel and then, for any that fail, runs the fix commands, serialized where they share files.
+Set `HK=0` to bypass hk’s installed Git hook launcher for one command, for example `HK=0 git commit`. This is handled by the launcher, not by every direct hk invocation.
-If disabled, hk skips the check pass and runs the overlapping fix commands in series.
+## `HK_CACHE` {#hk-cache}
-## `HK_CONFIG_DIR`
+**Type:** boolean · **Default:** true in release builds; false in debug builds
-Type: `path`
-Default: `$XDG_CONFIG_HOME/hk` (usually `~/.config/hk`)
+Enable or disable the evaluated-configuration cache. Use `HK_CACHE=0 hk validate` when diagnosing stale configuration, or `hk cache clear` to remove hk’s cache.
-The directory hk uses for user configuration such as `~/.config/hk/config.pkl`.
+## `HK_CACHE_DIR` {#hk-cache-dir}
-## `HK_DISPLAY_SKIP_REASONS`
+**Type:** path · **Default:** platform cache directory plus `hk`
-Type: `string[]` (comma-separated list)
-Default: `profile-not-enabled`
+Directory for cached configuration and other cache files. On Linux this is typically `~/.cache/hk`; on macOS it is typically `~/Library/Caches/hk`.
-Controls which skip reasons are displayed when steps are skipped. A step skipped for a reason not in this list is skipped silently.
+## `HK_CHECK` {#hk-check}
-Available reasons:
+**Type:** boolean · **Default:** false
-- `profile-not-enabled`: a profile the step requires is not enabled (default)
-- `profile-explicitly-disabled`: a profile the step requires was disabled with `!profile`
-- `disabled-by-config`: the step is listed in `skip_steps` in `hk.pkl`, user config, or git config
-- `disabled-by-env`: the step is listed in `HK_SKIP_STEPS`
-- `disabled-by-cli`: the step was skipped with `--skip-step`
-- `no-command-for-run-type`: the step has no command for the current mode (for example no `fix` command during `hk fix`)
-- `no-files-to-process`: no files matched the step's globs
-- `condition-false`: the step's `condition` evaluated to false
-- `missing-required-env`: an environment variable listed in the step's `required` setting is unset
+Request check commands instead of fix commands. This setting wins when both `HK_CHECK` and `HK_FIX` are enabled; explicit `--check` and `--fix` flags take precedence. Check commands should leave files unchanged, but hk does not enforce that convention.
-Example: `HK_DISPLAY_SKIP_REASONS=profile-not-enabled,no-files-to-process`.
+## `HK_CHECK_FIRST` {#hk-check-first}
-## `HK_EXCLUDE`
+**Type:** boolean · **Default:** true
-Type: `string[]` (comma-separated list)
-Default: `(empty)`
+Allow read-only checks before fixes when steps overlap. A passing check can avoid a write lock; a failing check can narrow the files that need fixing. The step’s `check_first` setting also affects this optimization.
-A comma-separated list of glob patterns to exclude from processing. These patterns are unioned with exclude patterns from other configuration sources (git config, user config, project config). Supports both directory names and glob patterns.
+## `HK_CONFIG_DIR` {#hk-config-dir}
-Examples:
-```bash
-# Exclude specific directories
-HK_EXCLUDE=node_modules,dist
+**Type:** path · **Default:** `$XDG_CONFIG_HOME/hk` or `~/.config/hk`
-# Exclude using glob patterns
-HK_EXCLUDE="**/*.min.js,**/*.map"
-```
+Directory containing the user `config.pkl`. See [user configuration](/configuration#hkrc) for discovery and legacy paths.
-## `HK_FAIL_FAST`
+## `HK_DISPLAY_SKIP_REASONS` {#hk-display-skip-reasons}
-Type: `bool`
-Default: `true`
+**Type:** comma-separated strings · **Default:** `profile-not-enabled`
-If `true`, hk will abort running steps after the first one fails.
+Select skip reasons to display. Use `hk check --why ` for a detailed explanation of a particular step’s selection.
-## `HK_FILE`
+## `HK_EXCLUDE` {#hk-exclude}
-Type: `string`
-Default: `hk.pkl`
+**Type:** comma-separated patterns · **Default:** empty
-The config file to use. Setting this skips the normal [config file search](/configuration#config-file-paths).
+Exclude files or directories from processing. These patterns combine with exclusions from other sources. Example: `HK_EXCLUDE='node_modules,dist,**/*.min.js' hk check --all`.
-## `HK_FIX`
+## `HK_FAIL_FAST` {#hk-fail-fast}
-Type: `bool`
-Default: `true`
+**Type:** boolean · **Default:** true
-If set to `false`, hooks run their check commands instead of their fix commands. Passing `--fix` overrides it for a single run, and `--check` or [`HK_CHECK`](#hk-check) forces check commands. `git config hk.fix` sets the same value at a lower precedence.
+Stop remaining work after a failure. Use `HK_FAIL_FAST=0 hk check --all` or `--no-fail-fast` to collect failures from remaining steps.
-## `HK_HIDE_WARNINGS`
+## `HK_FILE` {#hk-file}
-Type: `string[]` (comma-separated list)
-Default: `(empty)`
+**Type:** path · **Default:** automatic project config discovery
-A comma-separated list of warning tags to suppress. This allows you to hide specific warning messages that you don't want to see.
+Select a specific Pkl configuration instead of searching for `hk.local.pkl` or `hk.pkl`. Example: `HK_FILE=./config/ci.pkl hk check --all`.
-Available warning tags:
+## `HK_FIX` {#hk-fix}
-- `missing-profiles`: Suppresses warnings about steps being skipped due to missing profiles
+**Type:** boolean · **Default:** true
-Example usage:
+Permit fix mode when the hook requests it. Setting this to `false` disables configured fixes unless an explicit fix flag overrides it. A value of `true` does not make a normal `hk check` run fixes. `HK_CHECK=1` takes precedence when both settings are enabled.
-```bash
-HK_HIDE_WARNINGS=missing-profiles hk check
-```
+## `HK_HIDE_WARNINGS` {#hk-hide-warnings}
-## `HK_HIDE_WHEN_DONE`
+**Type:** comma-separated tags · **Default:** empty
-Type: `bool`
-Default: `false`
+Suppress named warning categories, such as `missing-profiles`. Suppressed tags combine across configuration sources.
-If set to `true`, hk will hide the progress output when the hook finishes if there are no errors.
+## `HK_HIDE_WHEN_DONE` {#hk-hide-when-done}
-## `HK_JOBS`
+**Type:** boolean · **Default:** false
-Type: `usize`
-Default: `(number of cores)`
+Hide progress output after a successful hook finishes. Failed runs keep their diagnostics.
-The number of jobs to run in parallel. `HK_JOB` is accepted as an alias.
+## `HK_JOBS` {#hk-jobs}
-## `HK_JSON`
+**Type:** nonnegative integer · **Default:** 0 (detect CPU count)
-Type: `bool`
-Default: `false`
+Limit concurrent hk jobs. Example: `HK_JOBS=4 hk check --all`. Linters can also start their own workers, so increasing this value does not always improve speed.
-Enables JSON output format for structured data, equivalent to passing `--json`. Useful for integration with other tools or for programmatic processing of results.
+## `HK_JSON` {#hk-json}
-Example: `hk check --json | jq '.steps[] | select(.failed)'`
+**Type:** boolean · **Default:** false
-## `HK_LIBGIT2`
+Request JSON output for commands that support it. For execution plans, use `hk check --plan --json`. For trace events, use `HK_TRACE=json`; this setting does not turn arbitrary linter output into structured results.
-Type: `bool`
-Default: `true`
+## `HK_LIBGIT2` {#hk-libgit2}
-If set to `false`, hk shells out to the `git` command instead of using libgit2. This may perform better
-in some cases, such as repositories that use `fsmonitor` to watch for changes.
+**Type:** boolean · **Default:** true
-## `HK_LOG`
+Use libgit2 for Git operations where supported. Set `HK_LIBGIT2=0` to use the Git CLI backend, for example when comparing performance with Git’s fsmonitor integration.
-Type: `off` | `error` | `warn` | `info` | `debug` | `trace`
-Default: `info`
+## `HK_LOG` {#hk-log}
-The log level to use. `HK_LOG_LEVEL` is accepted as an alias.
+**Type:** log level · **Default:** `info`
-## `HK_LOG_FILE`
+Console log level: `off`, `error`, `warn`, `info`, `debug`, or `trace`. `HK_LOG_LEVEL` is also accepted. Use `hk check -v` for debug output or `-vv` for trace logging.
-Type: `path`
-Default: `~/.local/state/hk/hk.log`
+## `HK_LOG_FILE` {#hk-log-file}
-The log file to use.
+**Type:** path · **Default:** `$HK_STATE_DIR/hk.log`
-## `HK_LOG_FILE_LEVEL`
+Log file location. Example: `HK_LOG_FILE=/tmp/hk.log hk check`.
-Type: `off` | `error` | `warn` | `info` | `debug` | `trace`
-Default: `HK_LOG`
+## `HK_LOG_FILE_LEVEL` {#hk-log-file-level}
-The log level to use for the log file.
+**Type:** log level · **Default:** the environment’s log level
-## `HK_MISE`
+Choose a separate verbosity for file logs. Example: `HK_LOG_FILE_LEVEL=trace hk check`.
-Type: `bool`
-Default: `false`
+## `HK_MISE` {#hk-mise}
-If set to `true`:
+**Type:** boolean · **Default:** false
-- `hk install` installs hooks that run hk through `mise x`, so mise-managed tools are available without activating mise in the shell
-- `hk init` also creates a `mise.toml` file with hk configured
-- Steps with a `dir` get the mise environment for that directory (`mise env`, cached per directory), so tools and env vars from the directory's mise config are available. See [mise integration](/mise_integration#per-directory-environments-monorepos)
+Make `hk install` use `mise x` in hook launchers and make `hk init` create a starter `mise.toml` when absent. Reinstall hooks to update an existing launcher. See [mise integration](/mise_integration). Steps also receive the mise environment for their working directory; explicit step environment values take precedence. See [per-directory environments](/mise_integration#per-directory-environments-monorepos).
-## `HK_OUTPUT_FILE`
+## `HK_OUTPUT_FILE` {#hk-output-file}
Type: `path`
Default: `~/.local/state/hk/output.log`
The file where hk writes the complete output of a failed command. An empty value uses the default location.
+## `HK_PKL_BACKEND` {#hk-pkl-backend}
-## `HK_PKL_BACKEND`
-
-Type: `pkl` | `pklr`
-Default: `pklr`
-
-Selects the evaluator used to read `hk.pkl`. Set to `pkl` to use the pkl CLI instead of the built-in pklr evaluator.
-
-## `HK_PKL_CA_CERTIFICATES`
-
-Type: `path`
-
-A path to a CA certificates file to provide `pkl`'s `--ca-certificates` flag when invoking `pkl`.
-
-This is useful in corporate environments with SSL-intercepting proxies where pkl needs to trust custom CA certificates to download packages.
+**Type:** `pklr` or `pkl` · **Default:** `pklr`
-This variable is read directly from the environment before pkl is invoked, so it cannot be configured in `hk.pkl`.
+Choose the built-in evaluator or the separately installed Pkl CLI. Example: `HK_PKL_BACKEND=pkl hk validate`. See [Pkl evaluators](/pkl_introduction#evaluators).
-## `HK_PKL_CACHE_DIR`
+## `HK_PKL_CACHE_DIR` {#hk-pkl-cache-dir}
Type: `path`
Default: the platform cache directory with `pklr` appended (`~/.cache/pklr` on Linux, `~/Library/Caches/pklr` on macOS, and `%LOCALAPPDATA%\pklr` on Windows). Falls back to `~/.cache/pklr` when the platform cache directory is unavailable.
@@ -229,7 +159,13 @@ The directory used by the built-in pklr evaluator to persist downloaded Pkl pack
This variable is read directly from the environment before `hk.pkl` is evaluated, so it cannot be configured in `hk.pkl`.
-## `HK_PKL_EMBEDDED`
+## `HK_PKL_CA_CERTIFICATES` {#hk-pkl-ca-certificates}
+
+**Type:** path · **Default:** unset
+
+With the Pkl CLI backend, pass a CA certificate file through Pkl’s `--ca-certificates` flag. This must be set before configuration is evaluated.
+
+## `HK_PKL_EMBEDDED` {#hk-pkl-embedded}
Type: `bool`
Default: `true`
@@ -240,15 +176,13 @@ Set to `0` to disable seeding. The package is then resolved from `HK_PKL_CACHE_D
This variable is read directly from the environment before `hk.pkl` is evaluated, so it cannot be configured in `hk.pkl`.
-## `HK_PKL_HTTP_REWRITE`
-
-Type: `string`
+## `HK_PKL_HTTP_REWRITE` {#hk-pkl-http-rewrite}
-A value to provide `pkl`'s `--http-rewrite` flag when invoking `pkl`, in the form `http(s):///=http(s):///`.
+**Type:** string · **Default:** unset
-This variable is read directly from the environment before pkl is invoked, so it cannot be configured in `hk.pkl`.
+With the Pkl CLI backend, pass a URL rewrite through Pkl’s `--http-rewrite` flag. The value has the form `https://source.example/=https://mirror.example/` and must be set before evaluation.
-## `HK_PKL_OFFLINE`
+## `HK_PKL_OFFLINE` {#hk-pkl-offline}
Type: `bool`
Default: `false`
@@ -257,177 +191,96 @@ Disables network access in the built-in pklr evaluator. Package imports already
This variable is read directly from the environment before `hk.pkl` is evaluated, so it cannot be configured in `hk.pkl`.
-## `HK_PROFILE`
+## `HK_PROFILE` {#hk-profile}
-Type: `string[]` (comma-separated list)
+**Type:** comma-separated profile names · **Default:** empty
-The profile(s) to enable. Prefix a profile with `!` to explicitly disable it. `HK_PROFILES` is accepted as an alias.
+Enable profiles such as `slow` or `types`. Prefix a name with `!` to disable it. `HK_PROFILES` is also accepted. A step requires all of its positive profiles. Example: `HK_PROFILE=ci,slow hk check --all`.
-Example usage:
+## `HK_SKIP_HOOK` {#hk-skip-hook}
-- `HK_PROFILE=ci` - Enable the CI profile
-- `HK_PROFILE=slow,ci` - Enable multiple profiles
+**Type:** comma-separated hook names · **Default:** empty
-## `HK_SKIP_HOOK`
+Skip entire hooks, for example `HK_SKIP_HOOK=pre-push git push`. `HK_SKIP_HOOKS` is also accepted. Skip lists combine with Git and Pkl configuration.
-Type: `string[]` (comma-separated list)
-Default: `(empty)`
+## `HK_SKIP_STEPS` {#hk-skip-steps}
-A comma-separated list of hook names to skip entirely.
-For example: `HK_SKIP_HOOK=pre-commit,pre-push` skips those hooks completely. `HK_SKIP_HOOKS` is accepted as an alias.
+**Type:** comma-separated step names · **Default:** empty
-This is useful when you want to temporarily disable certain hooks while still keeping them configured in your `hk.pkl` file.
-Unlike `HK_SKIP_STEPS` which skips individual steps, this skips the entire hook and all its steps.
+Skip named steps in any hook, for example `HK_SKIP_STEPS=eslint hk check`. `HK_SKIP_STEP` is also accepted. Skip lists combine across configuration sources.
-This setting can also be configured via:
-- Git config: `git config hk.skipHook "pre-commit"`
-- User config (`~/.config/hk/config.pkl`): `skip_hooks = List("pre-commit")`
+## `HK_STAGE` {#hk-stage}
-All skip configurations from different sources are unioned together.
+**Type:** boolean · **Default:** the hook’s staging setting
-## `HK_SKIP_STEPS`
+Override automatic staging of fixes. Set `HK_STAGE=0` to leave fixes for review. See [reviewing fixes](/hooks#review-fixes-before-committing).
-Type: `string[]` (comma-separated list)
+## `HK_STASH` {#hk-stash}
-A comma-separated list of step names to skip in every hook.
-For example: `HK_SKIP_STEPS=lint,test` skips any steps named "lint" or "test". `HK_SKIP_STEP` is accepted as an alias.
+**Type:** `git`, `patch-file`, or `none` · **Default:** the hook’s setting, otherwise `none`
-This setting can also be configured via:
-- Git config: `git config hk.skipSteps "step1,step2"`
-- User config (`~/.config/hk/config.pkl`): `skip_steps = List("step1", "step2")`
+Override how unstaged work is saved before a hook. `git` enables stashing; `patch-file` currently uses the same Git implementation; `none` leaves unstaged work in place. Boolean `true`/`1` and `false`/`0` are also accepted. `hk init` explicitly configures Git stashing for pre-commit. See [stashing](/hooks#stashing-and-partial-commits).
-All skip configurations from different sources are unioned together.
+## `HK_STASH_BACKUP_COUNT` {#hk-stash-backup-count}
-## `HK_STAGE`
+**Type:** nonnegative integer · **Default:** 20
-Type: `bool`
+Number of backup patches to retain per repository under `$HK_STATE_DIR/patches/`. Set to `0` to disable patch backups.
-When set, overrides the [hook's `stage` key](/configuration#hooks-hook-stage-boolean), which controls whether hk automatically stages files modified by fix commands.
+## `HK_STASH_UNTRACKED` {#hk-stash-untracked}
-This is useful when you want to manually review changes made by auto-fixers before including them in your commit.
+**Type:** boolean · **Default:** true
-## `HK_STASH`
+Include untracked files when stashing. Setting this to `false` also skips untracked-file discovery entirely: those files will not appear in status-based reports or normal `hk check --all` selection. This can reduce scan time for very large worktrees, such as dotfiles repositories rooted at the home directory.
-Type: `git` | `patch-file` | `none`
-Default: `none`
+## `HK_STATE_DIR` {#hk-state-dir}
-Overrides the [hook-level `stash` setting](/configuration), which defaults to `none`.
+**Type:** path · **Default:** platform state directory plus `hk`
-- `git`: Use `git stash` to stash unstaged changes before running hooks.
-- `patch-file`: Currently an alias of the `git` behavior.
-- `none`: Do not stash unstaged changes before running hooks. Fastest option, but fix steps may modify unstaged changes if they are in the same file as staged changes.
+Directory for logs and stash backup patches. It typically resolves to `~/.local/state/hk` on Linux; hk also uses that fallback on platforms without a state-directory convention.
-In `hk.pkl`, the hook-level `stash` key also accepts booleans: `true` is an alias of `"git"` and `false` is an alias of `"none"`.
+## `HK_SUMMARY_TEXT` {#hk-summary-text}
-## `HK_STASH_BACKUP_COUNT`
+**Type:** boolean · **Default:** false
-Type: `usize`
-Default: `20`
+In plain-text mode, hk prints summaries for failed steps by default. Set to `true` to include successful-step summaries too; their output normally streams during execution.
-Number of backup patch files to keep per repository when stashing. Each time hk stashes changes, it writes a backup patch file to `$HK_STATE_DIR/patches/`; the oldest backups beyond this count are deleted automatically.
+## `HK_TERMINAL_PROGRESS` {#hk-terminal-progress}
-Set to `0` to disable patch backup creation entirely.
+**Type:** boolean · **Default:** true
-## `HK_STASH_UNTRACKED`
+Send progress updates through OSC sequences to compatible terminals. Disable this if the terminal renders those updates incorrectly.
-Type: `bool`
-Default: `true`
+## `HK_TIMING_JSON` {#hk-timing-json}
-If set to `true`, hk will stash untracked files when stashing before running hooks.
+**Type:** path · **Default:** unset
-When set to `false`, hk also skips the untracked-file scan entirely (`git status --untracked-files=no`). This is the recommended setting when `GIT_WORK_TREE` points at a very large directory such as `$HOME` (e.g. a YADM dotfiles repo), where scanning for untracked files can take tens of seconds. Untracked files will not appear in reports or `hk check --all` results in this mode.
+Write total and per-step wall time as JSON after a hook finishes. Example: `HK_TIMING_JSON=hk-timing.json hk check --all`. See [timing reports](/logging#a-run-is-slow).
-## `HK_STATE_DIR`
+## `HK_TRACE` {#hk-trace}
-Type: `path`
-Default: `~/.local/state/hk`
+**Type:** `1`, `true`, or `json` · **Default:** off
-The state directory to use.
+Enable text tracing with `HK_TRACE=1`, or JSON trace events with `HK_TRACE=json`. Text goes to standard error; JSON events go to standard output. See [tracing](/logging#tracing).
-## `HK_SUMMARY_TEXT`
+## `HK_WALK_IGNORE` {#hk-walk-ignore}
-Type: `bool`
-Default: `false`
+**Type:** boolean · **Default:** true
-Controls whether per-step output summaries are printed in plain text mode. By default, text mode only prints summaries for **failed** steps, so CI logs always include the full diagnostics for a failure. Successful steps stream their output during execution, so a trailing summary would only duplicate it. Set this to `true` to print a summary for every step in text mode.
+Respect `.gitignore` and other ignore files during directory walks. This affects discovery; other file filters and step exclusions still apply.
-Example:
+## `HK_WARNINGS` {#hk-warnings}
-```bash
-HK_SUMMARY_TEXT=1 hk check
-```
+**Type:** comma-separated warning tags · **Default:** empty
-## `HK_TERMINAL_PROGRESS`
+Enable opt-in warning categories, currently including `missing-profiles`. In Pkl, use `warnings = List("missing-profiles")`.
-Type: `bool`
-Default: `true`
-
-Enables or disables reporting progress via OSC sequences to compatible terminals.
-
-## `HK_TIMING_JSON`
-
-Type: `path`
-
-If set to a file path, hk will write a JSON timing report at the end of a run. The report includes total wall time and per-step wall time, with overlapping intervals merged so time isn't double-counted across parallel step parts.
-
-The `steps` field is an object mapping step names to an object with:
-
-- `wall_time_ms`: merged wall time in milliseconds
-- `profiles` (optional): the list of profiles required for that step. If there are no profiles, this field is omitted.
-
-Example usage:
-
-```bash
-HK_TIMING_JSON=/tmp/hk-timing.json hk check
-```
+## `HK_REPORT_JSON` {#hk-report-json}
-Additionally, when a hook-level `report` command is configured in `hk.pkl`, hk will set `HK_REPORT_JSON` to the same timing JSON content (in-memory) and execute the command after the hook finishes. This enables custom scripts to post-process or upload timing data without reading a file.
-
-Example output shape:
-
-```json
-{
- "total": { "wall_time_ms": 12456 },
- "steps": {
- "lint": { "wall_time_ms": 4321, "profiles": ["ci", "fast"] },
- "fmt": { "wall_time_ms": 2100 }
- }
-}
-```
-
-## `HK_TRACE`
-
-Type: `off` | `text` | `json` | `1` | `true`
-Default: `off`
-
-Enables tracing spans and performance diagnostics for detailed execution analysis.
-
-- `off`: No tracing (default)
-- `text` (or the aliases `1` / `true`): Human-readable trace output
-- `json`: Machine-readable JSON trace output
-
-Example: `HK_TRACE=text hk check` to see detailed execution traces.
-
-## `HK_WALK_IGNORE`
-
-Type: `bool`
-Default: `true`
-
-Controls whether hk respects `.gitignore` and other ignore files when walking directories.
-
-When enabled (default), hk will skip files matching patterns in `.gitignore`, `.ignore`, and other standard ignore files when discovering files for linting. This improves performance by not processing generated files, build artifacts, or vendored dependencies.
-
-When disabled, all files are included regardless of ignore patterns.
-
-Example: `HK_WALK_IGNORE=0 hk check --all` to include all files.
-
-## `HK_WARNINGS`
-
-Type: `string[]` (comma-separated list)
-Default: `(empty)`
-
-Warning tags to enable. This can also be configured in `hk.pkl` or user config:
+hk sets this variable for a hook’s `report` command. It contains the same timing data that `HK_TIMING_JSON` writes to a file. It is an output supplied to the report command, not a setting for users to configure.
```pkl
-warnings = List("missing-profiles")
+report = "node scripts/report-timings.js"
```
+
+The script can read `process.env.HK_REPORT_JSON`. See [timing reports](/logging#a-run-is-slow) for the JSON shape.
diff --git a/docs/getting_started.md b/docs/getting_started.md
index 8d973c401..757eff4d0 100644
--- a/docs/getting_started.md
+++ b/docs/getting_started.md
@@ -1,87 +1,86 @@
-# Getting Started
+---
+description: Install hk, configure your first checks, and run the same steps in Git hooks and CI.
+---
-This guide takes you from installing hk to running your first checks. Most projects can be set up
-in a few minutes.
+# Getting started
-## 1. Install hk
+Set up hk in an existing Git repository, then use the same linters when you commit, work locally, and run CI.
-From the project you want to configure, install hk with [mise](https://mise.jdx.dev/):
+## Installation
-```sh
-cd my-project
+Choose one installation method:
+
+::: code-group
+
+```sh [mise]
mise use hk
-hk --version
```
-Other installation methods:
-
-- [Homebrew](https://formulae.brew.sh/formula/hk): `brew install hk`
-- Cargo: `cargo install hk`
-- [Aqua](https://github.com/aquaproj/aqua-registry/blob/main/pkgs/jdx/hk/registry.yaml): `aqua g -i jdx/hk`
+```sh [Homebrew]
+brew install hk
+```
-hk uses its built-in Pkl evaluator by default, so you do not need to install the Pkl CLI. See the
-[Pkl introduction](/pkl_introduction) if you want to use the standalone evaluator or learn the
-configuration language.
+```sh [Cargo]
+cargo install hk --locked
+```
-## 2. Enable git hooks
+:::
-With Git 2.54 or newer, install hk once for all repositories on your machine:
+Verify the installation:
```sh
-hk install --global
+hk --version
```
-The global hooks are a silent no-op in repositories without an `hk.pkl`, so enabling them does not
-require every repository to use hk.
-
-On older Git versions, or when you only want hk in one repository, skip this step for now. You will
-install the repository hooks after creating `hk.pkl` in the next step.
+Prebuilt binaries are also available from [GitHub releases](https://github.com/jdx/hk/releases). hk uses the built-in [pklr evaluator](/pkl_introduction#evaluators) by default, so you do not need to install the Pkl CLI.
-::: warning Avoid duplicate hooks
-Do not combine `hk install --global` with a per-repository install unless you intentionally want both.
-Git combines hook commands from its global and local configuration, which can cause hk to run twice.
-:::
-
-## 3. Create a project configuration
+## Project setup
-From the project root, run:
+From the root of your repository, generate a configuration:
```sh
hk init
```
-hk detects common project files and creates an `hk.pkl` using the relevant
-[built-in linters](/builtins). To review and select linters and hooks yourself, use:
+hk detects tools from project files and creates `hk.pkl`. Review its steps before running them. To select tools and hooks yourself, use `hk init --interactive`.
-```sh
-hk init --interactive
-```
+::: tip Make the linters available
+Builtins configure commands; they do not install the tools they invoke. Install the selected linters with your project’s package manager or [mise](/mise_integration), and make sure hk can find them on `PATH`.
+:::
-Builtins define how hk invokes a tool; they do not install the tool itself. Each selected linter or
-formatter must be available on `PATH`. If you use mise, the [mise integration](/mise_integration)
-can manage those tool versions with the project.
+## Install hooks
-If you skipped the global installation, install the hooks defined by the new configuration now:
+Choose the scope that fits your setup:
-```sh
-hk install
-```
+| Scope | Command | Behavior |
+| --------------------------- | --------------------- | -------------------------------------------------------------------------------------- |
+| All repositories, Git 2.54+ | `hk install --global` | Install once in your user Git config; projects without an hk configuration are skipped |
+| Current repository | `hk install` | Install the hooks defined in this project; supports older Git versions |
+
+On Git 2.54+, hk uses Git’s configuration-based hooks. On older Git, a per-repository install writes script shims. Use `hk install --legacy` to request shims explicitly.
+
+If hk is already installed globally, `hk install` skips the local installation and cleans up stale local hk hooks. `--force-local` overrides that behavior, but combining local and global hooks can cause duplicate runs.
+
+::: tip Using mise tools in Git hooks
+Use `hk install --mise` or `hk install --global --mise` to launch hooks through `mise x`. This makes project tools available even when Git runs outside an activated shell, such as from an editor. mise itself must be on Git’s `PATH`.
+:::
+
+Commit `hk.pkl` so your team can share the configuration. Hook installation is local to each developer’s machine or clone.
-Per-repository installation uses Git's config-based hooks on Git 2.54 or newer and `.git/hooks/`
-scripts on older versions. See [`hk install`](/cli/install) for all installation modes, including
-`--legacy` and `--mise`.
+To remove an installation, use `hk uninstall` or `hk uninstall --global`. See the [install reference](/cli/install) for all options.
-## 4. Review `hk.pkl`
+## Your first configuration
-A typical configuration shares the same linters between automatic hooks and manual commands:
+This complete example runs Prettier, ESLint, and Ruff. Install and configure those tools first, or replace them with [builtins](/builtins) that match your project.
```pkl
amends "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Config.pkl"
import "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Builtins.pkl"
local linters = new Mapping {
- ["eslint"] = Builtins.eslint
["prettier"] = Builtins.prettier
+ ["eslint"] = Builtins.eslint
+ ["ruff"] = Builtins.ruff
}
hooks {
@@ -90,9 +89,7 @@ hooks {
stash = "git"
steps = linters
}
- ["check"] {
- steps = linters
- }
+ ["check"] { steps = linters }
["fix"] {
fix = true
steps = linters
@@ -100,84 +97,55 @@ hooks {
}
```
-In this example, `pre-commit` and `hk fix` may modify files. During a commit, `stash = "git"`
-protects unstaged changes while fixes are applied to staged content.
-
-See the [configuration guide](/configuration) for custom commands, dependencies, profiles, groups,
-and monorepo support. The [configuration examples](/reference/examples/) include complete JavaScript,
-Python, custom-linter, and monorepo setups.
-
-## 5. Test the setup
+The `amends` line loads hk’s configuration schema. `Builtins` supplies reusable step definitions. The local `linters` mapping lets all three hooks use the same steps.
-Run checks without making changes:
-
-```sh
-hk check --all
-```
+In this configuration, `pre-commit` fixes staged files while unstaged work is stashed. `check` checks your working tree, and `fix` applies fixes to it. Steps whose file patterns do not match any selected files are skipped.
-Apply available fixes:
+Validate the configuration without running its linters:
```sh
-hk fix --all
+hk validate
```
-To test the pre-commit configuration independently of its Git installation:
+## Checking and fixing code
```sh
-hk run pre-commit
+hk check # Check modified files
+hk fix # Apply available fixes
+hk check --all # Check all files, useful for CI
+hk check src/main.ts # Check a specific file
+hk check --step eslint
```
-`pre-commit` selects staged files by default. Running it without `--staged` also preserves the
-hook's configured stashing behavior for partially staged files. This validates the configured
-steps; make a test commit when you need to verify the installed Git hook end to end.
-
-By default, `hk check` and `hk fix` only operate on modified files. Use `--all` in CI or when you
-want to verify the entire repository, and `--from-ref main` to check files changed since a branch or
-commit.
+With the configuration above, modified files include staged, unstaged, and untracked files. `--all` selects tracked files plus eligible untracked files; ignore rules and exclusions still apply. Hook settings and flags can change file selection.
-## Common next steps
+Check commands should be read-only. Fix commands may edit files, and some findings need a manual fix. Fixes may be staged automatically. Review `git diff` and `git diff --cached`, or use `hk fix --no-stage` to leave fixes unstaged.
-- Browse and customize [built-in linters](/builtins).
-- Learn how hk selects files and schedules steps in [Hooks](/hooks).
-- Add hk to a mise-managed project with [`hk init --mise`](/mise_integration#hk-init-mise).
-- Configure a shared user-level `~/.config/hk/config.pkl` in [hkrc](/configuration#hkrc).
-- Diagnose a configuration with [`hk config explain`](/cli/config/explain) and
- [`hk config sources`](/cli/config/sources).
+## Preview a run
-## Removing hooks
-
-Remove the installation using the same scope you used to install it:
+Use the plan to see which steps and files hk selects:
```sh
-hk uninstall --global # global installation
-hk uninstall # current repository
+hk check --plan
+hk check --why eslint
+hk check --all --plan --json
```
-## Manual Git configuration
-
-If you prefer not to run `hk install --global`, you can add hooks directly to `~/.gitconfig`:
+These commands do not execute the hook’s steps. See [troubleshooting](/logging) if a step is missing or behaves unexpectedly.
-```ini
-[hook "hk-pre-commit"]
- command = test "${HK:-1}" = "0" || hk run pre-commit --from-hook "$@"
- event = pre-commit
-[hook "hk-pre-push"]
- command = test "${HK:-1}" = "0" || hk run pre-push --from-hook "$@"
- event = pre-push
-[hook "hk-commit-msg"]
- command = test "${HK:-1}" = "0" || hk run commit-msg --from-hook "$@"
- event = commit-msg
-```
-
-`--from-hook` makes repositories without a matching hk configuration exit silently. The `HK`
-check provides a per-command escape hatch: use `HK=0 git commit` to bypass hk temporarily. If hk is
-installed through mise but is not automatically activated, replace `hk` with `mise x -- hk`.
+## Running hooks
-To disable one globally configured event for a particular repository:
+After installation, Git invokes configured hooks automatically. You can also invoke them directly:
```sh
-git config --local hook.hk-pre-commit.enabled false
+hk run pre-commit
```
-See the complete [`hk install` reference](/cli/install) for additional flags and installation
-behavior.
+A manual hook run uses the hook’s configured behavior, including fixes, staging, and stashing. To inspect it first, use `hk run pre-commit --plan`.
+
+## Next steps
+
+- [Git hooks and stashing](/hooks): control automatic fixes and partial commits.
+- [Continuous integration](/ci): check a full repository or a branch.
+- [Configuration examples](/reference/examples/): start from a JavaScript, Python, or monorepo setup.
+- [Configuration](/configuration): customize steps, profiles, and local overrides.
diff --git a/docs/glossary.md b/docs/glossary.md
index 31b559693..db7e95ae4 100644
--- a/docs/glossary.md
+++ b/docs/glossary.md
@@ -1,127 +1,61 @@
---
-outline: "deep"
+description: Definitions of hooks, steps, groups, profiles, file locks, stashing, and workspaces in hk.
---
# Glossary
-This glossary defines key terms used throughout hk documentation and configuration.
+## Builtin
-## Depends
+A reusable Pkl step definition supplied by hk. It describes how to invoke a linter or utility; external linter executables must be installed separately. [Browse builtins](/builtins).
-A step configuration property that specifies other steps that must complete successfully before this step can run. Dependencies control execution order and ensure prerequisites are met.
+## Check
-Example:
-```pkl
-steps {
- ["typecheck"] {
- depends = List("lint", "format") // Wait for lint and format to complete
- check = "tsc --noEmit"
- }
-}
-```
+A command that reports problems without modifying files. hk relies on this convention to run checks concurrently with shared read locks. `hk check` runs the configured `check` hook.
-See: [Step Dependencies](/configuration#step-depends-list-string)
+## Dependency
-## Group
-
-An organizational unit that contains multiple steps, allowing you to structure your configuration hierarchically. Groups help organize related steps together and can be used to create logical divisions like "frontend" and "backend" tasks.
+The `depends` property names steps that must finish before another step runs. Use it to establish order, such as `depends = "eslint"` on a formatter. [Dependencies](/configuration#dependencies-and-groups).
-Example:
-```pkl
-steps {
- ["frontend"] = new Group {
- steps {
- ["prettier"] = Builtins.prettier
- ["eslint"] = Builtins.eslint
- }
- }
-}
-```
+## File lock
-See: [Group Configuration](/configuration#group)
+Coordination for a file selected by a step within a hook run. Multiple checks can hold read locks; a writer needs exclusive access. Locks only cover the files hk knows the step uses. [Execution model](/why-hk#parallelism-needs-coordination).
-## Hook
+## Fix
-A git hook or custom command that runs a collection of steps. hk supports standard git hooks like `pre-commit`, `pre-push`, `commit-msg`, and `prepare-commit-msg`, as well as custom hooks like `check` and `fix` for manual execution.
+A command that may modify files to resolve problems. A fix can still fail if some findings need manual changes. `hk fix` runs the configured `fix` hook.
-Example:
-```pkl
-hooks {
- ["pre-commit"] {
- fix = true
- stash = "git"
- steps = linters
- }
-}
-```
+## Glob
-See: [Hooks](/hooks)
+A pattern that selects file paths, such as `*.py` or `src/**/*.ts`. A step’s patterns filter the files selected for the run; they do not force a changed-file run to scan the whole repository.
-## Job
+## Group
-One unit of parallel work. The jobs setting is the number of steps hk runs concurrently, which controls how many linting and formatting tasks can run at the same time. It can be configured via the `-j/--jobs` CLI flag or the `HK_JOBS` environment variable and defaults to the number of CPU cores.
+A collection of steps with a scheduling boundary: its children can run concurrently, while later groups wait. Groups can provide defaults such as `dir` and `prefix` for children. [Group defaults](/configuration#group).
-Example:
-```bash
-# Run with 4 parallel jobs
-hk check --jobs 4
+## Hook
-# Or via environment variable
-HK_JOBS=8 hk fix
-```
+A named collection of steps. Git hooks include `pre-commit` and `pre-push`; custom hooks such as `check` can be invoked manually. [Git hooks](/hooks).
-See: [HK_JOBS](/environment_variables#hk-jobs)
+## Job
-## Skip
+A unit of step execution. A step may create multiple jobs through batching or workspace selection. `--jobs` and `HK_JOBS` limit concurrency; tools may also start their own workers.
-A mechanism to bypass execution of specific steps or entire hooks. Steps can be skipped using the `HK_SKIP_STEPS` environment variable, while entire hooks can be skipped with `HK_SKIP_HOOK`.
+## Profile
-Examples:
-```bash
-# Skip specific steps
-HK_SKIP_STEPS=lint,test hk run pre-commit
+A label used to enable or disable steps, such as `slow` or `types`. Activate one with `--profile types` or `HK_PROFILE=types`. A step requires all of its positive profile names. [Profiles](/configuration#profiles).
-# Skip entire hooks
-HK_SKIP_HOOK=pre-commit,pre-push git commit
-```
+## Stage
-See: [HK_SKIP_STEPS](/environment_variables#hk-skip-steps), [HK_SKIP_HOOK](/environment_variables#hk-skip-hook)
+To add file content to Git’s index for the next commit. A hook can stage fixes automatically, or leave them for review with `stage = false`. This differs from a step’s `stage` property, which specifies file patterns to stage.
## Stash
-A strategy for temporarily saving unstaged changes before running hooks that might modify files. This keeps fixers from touching your unstaged work and keeps that work from being staged by accident.
-
-Stash strategies:
-- `git`: Uses `git stash`
-- `patch-file`: Currently an alias of the `git` strategy
-- `none`: No stashing (the default; fastest, but fixers may modify unstaged changes in partially staged files)
+Temporarily saved unstaged work. A hook with `stash = "git"` isolates staged content before running its steps and restores saved changes afterward. `"patch-file"` currently uses the same implementation. [Stashing and partial commits](/hooks#stashing-and-partial-commits).
-Example:
-```pkl
-hooks {
- ["pre-commit"] {
- stash = "git" // Stash unstaged changes while fixers run
- steps = linters
- }
-}
-```
+## Step
-See: [HK_STASH](/environment_variables#hk-stash)
+An individual check, formatter, or task within a hook. A step defines commands and can select files, declare dependencies, and require profiles. [Define a step](/configuration#define-a-step).
-## Step
+## Workspace
-An individual linting, formatting, or validation task that processes files. Steps are the fundamental units of work in hk, each defining commands to check and/or fix code. Steps can specify which files they operate on using glob patterns, and can have dependencies on other steps.
-
-Example:
-```pkl
-steps {
- ["eslint"] {
- glob = List("*.js", "*.ts")
- check = "eslint {{files}}"
- fix = "eslint --fix {{files}}"
- depends = List("prettier") // Run after prettier
- }
-}
-```
-
-See: [Step Configuration](/configuration#hooks-hook-steps-step-group)
+A project directory located through a marker such as `package.json` or `Cargo.toml`. `workspace_indicator` partitions selected files so a step can run once per matching workspace. [Workspaces](/configuration#workspaces).
diff --git a/docs/hooks.md b/docs/hooks.md
index 294c7e79c..14f4c8a22 100644
--- a/docs/hooks.md
+++ b/docs/hooks.md
@@ -1,28 +1,111 @@
-# Hooks
+---
+description: Configure Git hooks, understand staged-file selection, and control fixes, stashing, and execution order.
+---
-This page describes the behavior of the git hooks that hk supports. Each step provides a "check" and a "fix" command. "check" commands are read-only and can run in parallel. "fix" commands can edit files and block other "fix" or "check" commands on the same files from running at the same time. For performance reasons, hk does not enforce that "check" commands leave files untouched, so follow this convention yourself for hk to behave as expected.
+# Git hooks and stashing
-This read/write locking is what lets hk run hooks as fast as possible while staying safe.
+A hook is a named collection of steps. Git invokes installed hooks at specific events; `hk run ` invokes them directly. The `check` and `fix` hooks are also available through `hk check` and `hk fix`.
-## Hook Behavior
+For installation, see [getting started](/getting_started#install-hooks).
-With `fix = true`, an hk hook performs the following:
+## Check and fix commands
-* Stashes any untracked/unstaged changes if stashing is enabled. It is off by default; see [`HK_STASH`](/environment_variables#hk-stash)
-* Gathers the list of files with staged changes (or all files if running `hk run pre-commit --all`)
-* Runs linters and hook steps in parallel up to [`HK_JOBS`](/environment_variables#hk-jobs) at a time, with caveats:
- * `exclusive = true` steps wait until all previous steps have finished and block later steps from starting
- * if a step has dependencies, hk waits for them to complete before starting it
- * hk takes read/write locks on each file the step matches (according to its glob patterns) unless `stomp = true`
- * if `check_first = true` on the step, hk runs the "check" command first with read locks; if that fails, it runs the "fix" command with write locks on all the files
- * if the step has a `check_list_files` command, hk uses its output to narrow the files it takes write locks on and passes to "fix"
- * if `check_first = false` on the step, hk runs the "fix" command after taking write locks, blocking other steps on the same files. Avoid this configuration for performance reasons.
- * if any files were modified and match the `stage` globs, they are added to the git index (`stage` defaults to the step's `glob` for steps with a `fix` command)
-* Restores the stashed untracked/unstaged changes
+A step’s `check` command should report problems without editing files. Its `fix` command may edit them. A hook with `fix = true` uses the fix workflow; steps with only a check command can still validate files.
-If `fix = false`, hk only runs the `check` commands and does not need read/write locks, since nothing should be making modifications. Steps with [`check_failed_files = true`](/configuration#focus-checks-on-failing-files) first use `check_diff` or `check_list_files` to identify affected paths, then run the detailed `check` command only on that focused set.
+hk uses read locks for checks and write locks for fixes. These locks coordinate steps that select the same files. hk does not sandbox commands or detect every undeclared write, so a check that edits files can interfere with other steps.
-### Allowing a step to fail
+Use [structured output and command effects](/agents) when integrating checks with coding agents or automation.
+
+## File selection
+
+With the generated configuration:
+
+| Invocation | Default selection |
+| ---------------------------------------- | ------------------------------------------------------- |
+| `hk run pre-commit` | Staged files |
+| `hk check` / `hk fix` | Modified files: staged, unstaged, and untracked |
+| `hk check --all` | Tracked files and eligible untracked files |
+| `hk check --staged` | Staged paths, using their current working-tree contents |
+| `hk check --from-ref main --to-ref HEAD` | Paths changed between those references |
+
+Step patterns, exclusions, ignore rules, and settings further filter this selection. Enabling stashing also changes default selection to staged files and excludes untracked files from an `--all` run.
+
+::: warning Staged paths are not staged contents
+`--staged` does not stash unstaged changes. If a file contains both staged and unstaged edits, the command sees its working-tree contents. Use a hook configured with stashing when the staged version must be isolated.
+:::
+
+Use `hk run pre-commit --plan` to inspect selected steps and files before executing them.
+
+## Stashing and partial commits
+
+For a pre-commit hook that applies fixes, set both `fix` and `stash`:
+
+```pkl
+hooks {
+ ["pre-commit"] {
+ fix = true
+ stash = "git"
+ steps = linters
+ }
+}
+```
+
+hk saves unstaged work, runs the hook against the staged content, stages applicable fixes, and restores the saved work. This lets you use `git add -p` without intentionally including the rest of your edits.
+
+Linters still operate on whole files. Staging one hunk does not restrict a formatter to that hunk.
+
+### Choose a stash strategy
+
+| Value | Behavior |
+| ------------------- | --------------------------------------------------- |
+| `"git"` or `true` | Save unstaged changes with Git stashing |
+| `"patch-file"` | Currently an alias for the Git stash implementation |
+| `"none"` or `false` | Leave unstaged work in place |
+
+An unspecified hook stash setting defaults to `"none"`. `hk init` explicitly enables `"git"` for pre-commit. Override a run with `--stash` or [`HK_STASH`](/environment_variables#hk-stash).
+
+Untracked files are included in stashing by default. `HK_STASH_UNTRACKED=0` also disables their discovery, which can help very large worktrees but changes file selection.
+
+### If restoration fails
+
+Read hk’s error before changing the working tree. Inspect `git status`, `git diff`, `git diff --cached`, and `git stash list` to understand which changes are present.
+
+hk keeps backup patches under `$HK_STATE_DIR/patches/` when Git stashing is used; the `stash_backup_count` setting controls retention. Preserve the reported stash and backup until you have recovered and reviewed your work. Avoid blindly applying a stash again to files that already contain its changes.
+
+## Review fixes before committing
+
+The generated pre-commit hook stages applicable fixes automatically. To apply fixes but stop the commit for review:
+
+```pkl
+hooks {
+ ["pre-commit"] {
+ fix = true
+ stash = "git"
+ stage = false
+ fail_on_fix = true
+ steps = linters
+ }
+}
+```
+
+When a fixer changes a file, hk fails the hook and leaves the fixes for you to review and stage. Retry the commit afterward. `stage = false` alone disables staging without requiring the hook to fail.
+
+For a single run, use `--no-stage` to disable automatic staging.
+
+
+
+## Order steps deliberately
+
+Steps run concurrently, up to the job limit, unless coordination requires them to wait:
+
+- `depends = "eslint"` waits for the named step.
+- `exclusive = true` waits for earlier steps and blocks later ones until it finishes.
+- A `Group` creates a boundary: its children run together, and later groups wait.
+- Read/write locks coordinate steps that select overlapping files.
+
+Locks prevent simultaneous writes; they do not choose the final style when tools disagree. Configure compatible rules or declare an explicit dependency.
+
+## Allowing a step to fail
Set `allow_failure = true` on a step to run it and report a non-zero command
exit without failing the hook. This is narrower than bypassing the hook or
@@ -43,85 +126,49 @@ conditional on an environment variable:
allow the commit, while an ordinary `git commit` remains blocked by the same
failure.
-## `pre-commit`
+## Commit-message hooks
-Runs during `git commit`, before the commit is created.
+`commit-msg` runs after the message is prepared and before the commit is created. Use the built-in Conventional Commits check:
```pkl
-hooks {
- ["pre-commit"] {
- fix = true
- stash = "git"
- steps {
- ["cargo-fmt"] {
- glob = "*.rs"
- check_first = true
- check = "cargo fmt --check"
- fix = "cargo fmt"
- }
- ["cargo-clippy"] {
- glob = "*.rs"
- check_first = true
- check = "cargo clippy"
- fix = "cargo clippy --fix --allow-dirty --allow-staged"
- }
- }
- }
-}
-```
-
-## `prepare-commit-msg`
+amends "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Config.pkl"
+import "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Builtins.pkl"
-Runs during `git commit`, before the commit message editor opens. Useful for rendering a default commit message template.
-The `commit_msg_file`, `source`, and `sha` template variables are available in this hook. The raw git hook arguments are also available as `hook_args`.
-
-```pkl
hooks {
- ["prepare-commit-msg"] {
- steps {
- ["render-commit-msg"] {
- check = "echo 'default commit message' > {{commit_msg_file}}"
- }
- }
+ ["commit-msg"] {
+ steps {
+ ["conventional-commit"] = Builtins.check_conventional_commit
}
+ }
}
-
```
-## `commit-msg`
+The `commit_msg_file` template variable contains the message path. `prepare-commit-msg` also receives `source` and `sha` when Git supplies them. Use that hook to prepare or edit a message before the user’s editor opens.
-Runs during `git commit`, after the commit message has been written. Useful for validating the commit message.
-The `commit_msg_file` template variable is available in this hook. The raw git hook arguments are also available as `hook_args`.
+## Other Git events
-```pkl
-hooks {
- ["commit-msg"] {
- steps {
- ["validate-commit-msg"] {
- check = "grep -Eq '^(fix|feat|chore):' {{commit_msg_file}}"
- }
- }
- }
-}
-```
+hk has dedicated handlers for these events:
-## `post-checkout`
+| Event | Useful template variables |
+| -------------------- | --------------------------------------------------------- |
+| `pre-commit` | Staged file selection |
+| `pre-push` | `hook_args` (remote and URL), `hook_stdin` (updated refs) |
+| `commit-msg` | `commit_msg_file` |
+| `prepare-commit-msg` | `commit_msg_file`, `source`, `sha` |
+| `post-checkout` | `prev_head`, `new_head`, `is_branch_checkout` |
+| `post-merge` | `hook_args` (squash flag) |
+| `post-rewrite` | `hook_args` (command), `hook_stdin` (rewritten refs) |
+| `pre-rebase` | `hook_args` (upstream and optional branch) |
+| `post-commit` | No event-specific arguments |
-Runs after `git checkout` updates the worktree. The `prev_head`, `new_head`, and `is_branch_checkout` template variables are available in this hook. `is_branch_checkout` is a boolean value. The raw git hook arguments are also available as `hook_args`.
+Dedicated handlers also expose their raw arguments as `hook_args`. See the [run reference](/cli/run) for argument details. Custom hooks can be invoked by name; hooks without a dedicated handler receive an empty `hook_args` value.
-```pkl
-hooks {
- ["post-checkout"] {
- steps {
- ["restore-lfs"] {
- check = "git lfs post-checkout {{ hook_args }}"
- }
- }
- }
-}
-```
+## Skip a hook or step
-## Other Hooks
+```sh
+HK_SKIP_STEPS=eslint git commit
+HK_SKIP_HOOK=pre-push git push
+HK=0 git commit
+```
-Other git hooks are also supported. See .
-The raw arguments for hooks with dedicated handlers are available as `hook_args`; hooks without dedicated handlers get an empty `hook_args`.
+`HK=0` bypasses hk’s installed hook launcher. To persist a preference, use [Git configuration](/configuration#git-configuration), such as `git config --local hk.skipSteps eslint`.
diff --git a/docs/index.md b/docs/index.md
index c3c2343fa..5a44024d6 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,6 +1,8 @@
---
layout: page
-title: Fast git hooks and project linting
+sidebar: false
+title: Git hooks and checks that run together
+description: Run linters and formatters in parallel with file locking, staged-change stashing, reusable Pkl configuration, and mise integration.
---
diff --git a/docs/logging.md b/docs/logging.md
index f290b0bb2..ba5fd477c 100644
--- a/docs/logging.md
+++ b/docs/logging.md
@@ -1,206 +1,148 @@
---
-outline: "deep"
+description: Diagnose skipped steps, configuration problems, missing tools, hook failures, and slow runs.
---
-# Logging and Debugging
+# Troubleshooting
-hk provides several ways to control logging output for debugging issues and understanding what happens during execution.
+Start with a plan and verbose output. They usually show whether the problem is file selection, configuration, or a linter command.
-## Log Levels
+```sh
+hk check --plan
+hk check -v
+```
-hk supports standard log levels that control the amount of information displayed:
+## A step does not run
-- **error**: Only show error messages
-- **warn**: Show warnings and errors
-- **info**: Show informational messages, warnings, and errors (default)
-- **debug**: Show debug information including file operations and step execution details
-- **trace**: Show detailed trace information including all internal operations
+Ask hk to explain the step:
-## Setting Log Levels
+```sh
+hk check --why eslint
+hk check --all --why eslint
+```
-### Using CLI Flags
+Replace `eslint` with the name in your configuration. Check its file patterns, exclusions, required environment variables, conditions, and profiles.
-The simplest way to control logging is through command-line flags:
+`hk check` normally selects modified files. `--all` expands the selection. A step with a `slow` profile needs `--slow` or `--profile slow`; a step with multiple positive profiles requires all of them.
-```bash
-# Show debug output (includes what files are being checked/fixed)
-hk check -v
-hk fix --verbose
+To inspect the plan as JSON:
-# Show trace output (very detailed, includes all internal operations)
-hk check -vv
+```sh
+hk check --all --plan --json
```
-The `-v` flag can be used multiple times:
-- `-v` or `--verbose`: Sets log level to DEBUG
-- `-vv`: Sets log level to TRACE
+Plans do not run linter commands. Configuration evaluation and condition evaluation may still inspect the environment.
-### Using Environment Variables
+## Configuration is not what you expect
-You can also set the log level using the `HK_LOG` environment variable:
-
-```bash
-# Set log level to debug for a single command
-HK_LOG=debug hk check
-
-# Set log level to trace
-HK_LOG=trace hk fix
-
-# Export for all commands in the session
-export HK_LOG=debug
-hk check
-hk fix
+```sh
+hk validate -v
+hk config dump
+hk config get skip_steps
+hk config explain jobs
```
-### Log File Output
+`hk validate` checks the Pkl configuration. `hk config dump` shows effective runtime settings, rather than the complete hook and step definitions. `hk config explain` helps identify overrides.
-By default, hk writes logs to a file at `~/.local/state/hk/hk.log`. You can control this with environment variables:
+Check for `hk.local.pkl`, a user config, Git settings, and `HK_*` environment variables. See [configuration precedence](/configuration#configuration-precedence).
-```bash
-# Change the log file location
-HK_LOG_FILE=/tmp/my-hk.log hk check
+For evaluator or cache issues:
-# Set a different log level for the file (defaults to HK_LOG)
-HK_LOG_FILE_LEVEL=trace hk check
+```sh
+HK_CACHE=0 hk validate
+HK_PKL_BACKEND=pkl hk validate
```
-This is useful when you want minimal console output but detailed file logging for later analysis.
+The second command requires the Pkl CLI. See [Pkl evaluators](/pkl_introduction#evaluators).
-## Tracing and Performance Diagnostics
+## A command is missing
-hk includes built-in tracing support for performance analysis and detailed execution tracking.
+Builtins do not install linters. Check that the named executable is available in the environment running hk.
-### Enabling Tracing
+If it works in your terminal but fails from Git or an editor, install the launcher with `hk install --mise` when using mise. Git must still be able to find mise itself. For language package dependencies, expose the package’s executable directory. See [mise integration](/mise_integration).
-Use the `--trace` flag or `HK_TRACE` environment variable:
+## A hook does not fire, or fires twice
-```bash
-# Enable tracing with console output
-hk check --trace
-
-# Enable tracing via environment variable (text mode)
-HK_TRACE=1 hk check
-HK_TRACE=text hk check
+Inspect installation without rewriting it:
-# Enable tracing with JSON output for programmatic analysis
-HK_TRACE=json hk check > trace.jsonl
-hk check --trace --json > trace.jsonl
+```sh
+git config --show-origin --get-regexp '^hook\.hk-'
+git config --show-origin --get core.hooksPath
```
-### Trace Output Formats
+These commands may exit nonzero if no matching setting exists. On older Git or with `--legacy`, inspect the applicable hook scripts too.
-**Text Mode**: Human-readable hierarchical output showing spans and timing:
-```
- 0.123s INFO Starting check
- 0.456s ├─ lint::eslint
- 0.789s │ ├─ Running eslint on 45 files
- 1.234s │ └─ Complete (478ms)
- 1.567s └─ Complete (1.444s)
-```
+Use `hk run pre-commit --plan` to confirm the configured hook can be loaded. Run `hk install` to refresh a local installation, or `hk install --global` for a global one.
-**JSON Mode**: Outputs newline-delimited JSON (JSONL) for programmatic analysis:
-```json
-{"type":"meta","span_schema_version":1,"hk_version":"1.12.1","pid":12345}
-{"type":"span_start","ts_ns":123456,"id":"span_0","name":"check","attrs":{}}
-{"type":"span_start","ts_ns":456789,"id":"span_1","name":"lint","attrs":{"step":"eslint"},"parent_id":"span_0"}
-{"type":"span_end","ts_ns":789012,"id":"span_1"}
-{"type":"span_end","ts_ns":1234567,"id":"span_0"}
-```
+A normal local install detects existing global hk hooks and avoids duplicating them. An explicitly forced local install alongside global hooks can cause duplicate runs. See [installation](/getting_started#install-hooks).
+
+## A hook changes more than expected
-### Performance Timing Reports
+Compare `git diff` and `git diff --cached`. A staged path can contain unstaged edits, and formatters work on whole files.
-Generate JSON timing reports for analysis:
+Use `stash = "git"` to isolate staged content before a pre-commit fixer runs. Use `stage = false` with `fail_on_fix = true` to review fixes before committing. See [hooks and stashing](/hooks).
-```bash
-# Write timing report to a file
-HK_TIMING_JSON=/tmp/timing.json hk check
+## A run is slow
-# The report includes:
-# - Total wall time
-# - Per-step wall time (with overlapping intervals merged)
-# - Profile information for each step
+Write a timing report:
+
+```sh
+HK_TIMING_JSON=hk-timing.json hk check --all
```
-Example timing report:
+The report contains total and per-step wall time:
+
```json
{
"total": { "wall_time_ms": 12456 },
"steps": {
- "lint": { "wall_time_ms": 4321, "profiles": ["ci", "fast"] },
- "fmt": { "wall_time_ms": 2100 },
- "typecheck": { "wall_time_ms": 6035 }
+ "lint": { "wall_time_ms": 4321, "profiles": ["slow"] },
+ "format": { "wall_time_ms": 2100 }
}
}
```
-## Quiet and Silent Modes
-
-To reduce output:
+Step time merges overlapping intervals within that step. Different steps can overlap, so their durations do not sum to total run time.
-```bash
-# Suppress non-essential output (info messages, progress indicators)
-# Failed-step diagnostics are still shown
-hk check --quiet
-hk check -q
-
-# Suppress all output including warnings (only errors are shown)
-hk check --silent
-```
+Look for expensive linters, unnecessary `exclusive` settings, broad file patterns, and dependencies that serialize work. Compare with fewer jobs if the linters already parallelize internally. For very large worktrees, untracked-file discovery can also be costly; see [`HK_STASH_UNTRACKED`](/environment_variables#hk-stash-untracked).
-## Common Debugging Scenarios
+## Log levels
-### Debugging Step Execution
+| Level | Output |
+| ------- | ------------------------------------ |
+| `error` | Errors |
+| `warn` | Warnings and errors |
+| `info` | Informational messages; the default |
+| `debug` | File selection and execution details |
+| `trace` | More detailed internal operations |
-To see which files are being processed by each step:
-
-```bash
-# Use debug level to see file operations
+```sh
+hk check -v # Debug logging
+hk check -vv # Trace logging
HK_LOG=debug hk check
-
-# Use trace level for maximum detail
-HK_LOG=trace hk check
```
-### Debugging Performance Issues
+Use `--quiet` to reduce output or `--silent` to suppress it. Failed-step summaries remain useful in plain-text CI output; `HK_SUMMARY_TEXT=1` also requests summaries for successful steps.
-To identify slow steps:
+### Log files
-```bash
-# Generate a timing report
-HK_TIMING_JSON=/tmp/timing.json hk check
-cat /tmp/timing.json | jq .
+Logs go to `$HK_STATE_DIR/hk.log` by default. On Linux, this is typically `~/.local/state/hk/hk.log`. Override the file path and level independently:
-# Use tracing to see detailed timing
-hk check --trace
+```sh
+HK_LOG_FILE=/tmp/hk-debug.log HK_LOG_FILE_LEVEL=trace hk check
```
-### Debugging Configuration Issues
-
-To see how configuration is being loaded and processed:
+## Tracing
-```bash
-# Validate configuration with verbose output
-hk validate -v
+Tracing records spans and timing in addition to ordinary logs:
-# Check which steps would run without running them
-hk check --plan
+```sh
+hk check --trace
+HK_TRACE=1 hk check
+HK_TRACE=json hk check > trace.jsonl
```
-### Debugging Git Hook Issues
-
-When git hooks aren't working as expected:
-
-```bash
-# Test hooks directly with debug output
-HK_LOG=debug hk run pre-commit
-
-# Check hook installation
-hk install --verbose
-```
+Text tracing goes to standard error. JSON tracing writes events to standard output. Commands that print their own standard output may share that stream, so inspect it before treating the entire file as JSONL.
-## Tips
+## Report a bug
-1. **Start with `-v`**: For most debugging, `hk check -v` provides enough detail without overwhelming output
-2. **Use log files**: Set `HK_LOG_FILE_LEVEL=trace` to capture detailed logs without cluttering the console
-3. **Combine with other tools**: Pipe JSON trace output to tools like `jq` for analysis
-4. **Profile-specific debugging**: Use `HK_LOG=debug hk check --profile slow` to debug specific profiles
+Include the hk version, operating system, Git version, relevant configuration, exact command, and the first substantive error from verbose output. For file-selection or stashing issues, describe which files or hunks were staged. Review logs for private paths, command output, and environment values before posting them to [GitHub issues](https://github.com/jdx/hk/issues).
diff --git a/docs/mise_integration.md b/docs/mise_integration.md
index 99e1eab56..bd3e3d8ba 100644
--- a/docs/mise_integration.md
+++ b/docs/mise_integration.md
@@ -1,72 +1,116 @@
+---
+description: Use mise to provide hk and linter versions, environment variables, and Git hook execution.
+---
+
# mise integration
-Many git hook managers build in features that hk's sister project, [mise-en-place](https://github.com/jdx/mise), already provides. hk leaves those to mise, so use mise and hk together if you want
-any of the features described below.
+[mise](https://mise.jdx.dev/) manages tools, environments, and tasks. hk selects files and coordinates checks and fixes. Together they let a team share tool versions and run hooks from terminals, editors, and CI.
-To default hk to enable these mise features, set [`HK_MISE=1`](/environment_variables#hk-mise).
+mise is optional: hk can run any executable available on `PATH`.
-:::info
-Setting `HK_MISE=1` will wrap your Git hooks with `mise x`. This ensures that mise automatically sets up the correct environment and tool versions before running hk, even if other developers haven't activated mise in their shell.
-:::
+## Install tools
-## `hk init --mise`
+From your project directory:
-Use the `--mise` flag on `hk init` to have hk create a new `mise.toml`
-file in the root of the repository that installs hk and defines a `pre-commit` task, so users can run `mise run pre-commit` as a "shortcut" for `hk run pre-commit`. Of course, that's actually longer, but the advantage is that tasks can be used consistently for all project actions, not just git hooks.
+```sh
+mise use hk
+mise use npm:prettier
+```
-## `hk install --mise`
+Commit the resulting `mise.toml`. Other developers can run `mise install` to install the declared versions.
-Use the `--mise` flag on `hk install` to make the installed hooks run hk through `mise x`. This sets up the mise environment (namely, adding tools to `PATH`) before hk runs.
+For tools already managed by a language package manager, keep them there. For example, expose a Node project’s installed executables through mise:
-With `mise x`, other developers do not need mise activated in their shell to use the hooks. It's useful for working
-with developers who don't typically use mise but want hooks on a particular project to work with the tools defined in `mise.toml`.
+```toml
+[env]
+_.path = ["node_modules/.bin"]
+```
+
+Run the project’s package installation command before invoking hk. See [mise tool management](https://mise.jdx.dev/dev-tools/) for supported backends.
-## Tool Management
+## Make tools available to Git
-mise's tool management lets you pin the versions of all the tools used in `hk.pkl` in a single place. Run `mise use` for
-each tool you want to manage:
+Install hk’s hooks with mise integration:
```sh
-mise use hk
-mise use jq
-mise use npm:prettier
+hk install --mise
+```
+
+Or, on Git 2.54+, install globally:
+
+```sh
+hk install --global --mise
```
-This will create a `mise.toml` file that can be committed into the project. See the [mise dev tool docs](https://mise.jdx.dev/dev-tools/) for more information.
+The launcher uses `mise x` to prepare the project environment before running hk. Developers do not need an activated shell, but Git must be able to find `mise` itself.
+
+Setting `HK_MISE=1` makes `--mise` the default for later `hk init` and `hk install` commands. It does not rewrite an already-installed launcher until installation runs again.
+
+## Generate a starter setup
+
+`hk init --mise` creates `hk.pkl` and, when absent, a `mise.toml` with hk configured and a `pre-commit` task.
+
+```sh
+hk init --mise
+hk install --mise
+```
+
+Review the generated tools and tasks. Existing `mise.toml` files are preserved.
+
+## Install hooks when tools are installed
+
+Add a postinstall hook to the project’s `mise.toml`:
+
+```toml
+[hooks]
+postinstall = "hk install --mise"
+```
-## Task Management
+This installs or updates hooks when mise installs tools. If hk hooks are already installed globally, hk skips the local installation and cleans up stale local hooks.
-[mise tasks](https://mise.jdx.dev/tasks/) can be used inside hk steps.
-They provide dependency management, option parsing, parallel execution, and more.
+## Call a mise task from a step
-Run `mise run` in `hk.pkl` like any other command:
+Use a task when a check is also useful outside Git hooks:
```pkl
amends "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Config.pkl"
hooks {
- ["pre-commit"] {
- steps {
- ["prelint"] {
- check = "mise run prelint"
- exclusive = true // ensures this completes before the next steps
- }
- // ... more steps ...
- }
+ ["check"] {
+ steps {
+ ["test"] {
+ check = "mise run test"
+ }
}
+ }
}
```
-## Environment Variables
+A step without a `glob` runs regardless of file matches. Add a pattern if the task should only run for selected file types.
+
+If a task writes files outside the step’s selected paths, declare suitable dependencies or use `exclusive = true` to keep it from interfering with other steps.
-You can define an `[env]` section in `mise.toml` to set environment variables for the hooks:
+## Share environment variables
+
+Use mise’s `[env]` section for variables that should apply to project commands:
```toml
[env]
-PRETTIER_CONFIG = ".prettierrc.json"
+NODE_ENV = "development"
+```
+
+Use hk’s global, hook, or step `env` blocks for variables specific to linter commands. See [mise environments](https://mise.jdx.dev/environments/) and [hk configuration](/configuration).
+
+## Run in CI
+
+Once mise is available:
+
+```sh
+mise install
+mise exec -- hk check --all
```
-mise has much more functionality around environment variables, so see the [mise docs](https://mise.jdx.dev/environments/) for more information.
+Install language package dependencies too, if the steps use them. See [continuous integration](/ci) for branch comparisons, profiles, and diagnostics.
## Per-directory environments (monorepos)
@@ -91,27 +135,3 @@ hooks {
```
Explicit step `env` values always win over the mise-provided environment.
-
-## Recommended Setup
-
-The recommended approach is to use `mise.toml` as the source of truth for your tools and environment, while using `hk` specifically for managing the Git lifecycle.
-
-By setting `HK_MISE=1` and using a `postinstall` hook, you can automate hook installation for your entire team:
-
-```toml
-[tools]
-hk = "latest"
-# ... other tools like prettier, actionlint, etc.
-
-[env]
-HK_MISE = 1
-
-[hooks]
-# Automatically install/update hooks when tools are installed
-postinstall = "hk install --mise"
-```
-
-If hk is already configured globally (e.g. `hk install --global` from a
-dotfiles setup), `hk install` automatically skips the per-repo install
-and cleans up any stale local hooks, so it's safe to leave the
-`postinstall` line in place across machines with mixed setups.
diff --git a/docs/pkl_introduction.md b/docs/pkl_introduction.md
index 481696d86..1788bd983 100644
--- a/docs/pkl_introduction.md
+++ b/docs/pkl_introduction.md
@@ -1,195 +1,184 @@
-# Introduction to pkl
+---
+description: Learn the Pkl syntax needed to configure hk, reuse steps, and diagnose evaluation errors.
+---
-hk uses [pkl](https://pkl-lang.org/) for configuration. As pkl is a relatively new configuration language, this doc gives an overview of how to write
-it and work with it for hk configuration.
+# Pkl essentials
-## Dependencies
+hk uses [Pkl](https://pkl-lang.org/) for typed configuration. Most projects need only a few features: amend the schema, import builtins, define steps, and reuse them across hooks.
-hk uses a built-in pkl evaluator ([pklr](https://github.com/jdx/pklr)) by default, so you do not need to install the pkl CLI.
+Pkl evaluates configuration. hk then runs the commands that configuration defines.
-pklr may not support every pkl feature yet. If you run into issues with pklr, you can switch back to the pkl CLI with `HK_PKL_BACKEND=pkl`.
+## Start with the schema
-To use the pkl CLI backend, install pkl with mise:
+Every project configuration should amend hk’s base schema:
-```sh
-mise use -g pkl
+```pkl
+amends "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Config.pkl"
+import "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Builtins.pkl"
```
-## Why pkl?
+`amends` supplies the allowed properties and classes, such as `Step`, `Hook`, and `Group`. `import` makes another module available under its name, here `Builtins`.
-* Schema validation is built into the language so your IDE can display errors not just with pkl syntax, but ensure that the types are correct
-* pkl can import other pkl files from the file system or HTTP URLs—so hk doesn't need its own logic around "importing" files
-* You can create/amend shared objects which can really help clean up your config. It even has things like functions and string templates for advanced use cases.
-* pkl is comprehensive enough—but static—that I found I didn't need a plugin system for hk. I had looked at wasm and lua for plugins, but by using (cached) pkl files this helps hk stay much faster than it would be otherwise.
+Keep both package URLs on the same version. Changing the hk executable does not rewrite pinned imports in `hk.pkl`.
-## Downsides?
+## Values and local variables
-* Editor/syntax highlighting support is young—though being a project driven by Apple I suspect this will improve quicker than most languages
-* Some of the behavior with the "amends" line and how `hk.pkl` files are used in hk I wish was a little more streamlined—but this is more of an issue with hk than pkl.
-* It's more complex than simple formats like yaml or toml and there is more to learn, however:
- * AI tools make this much easier since you can just ask cursor or whoever to help you write pkl
- * It's complex because it has a lot of features that don't exist in simple formats
-* Some of the quirks of pkl I can't say I'm a fan of:
- * `List(a, b, c)` instead of `[a, b, c]`
- * `default` behavior is quite confusing
- * amending is a little weird
-
-I have looked at many other esoteric languages for a long time now for hk and other projects though. IMO schema validation being built in
-is an absolute killer feature that on its own is worth the tradeoffs. If you find yourself bristling at pkl, just remember that by using
-features in pkl that means a lot of features didn't need to be implemented in hk—so you'll just be learning pkl features instead of hk features.
+```pkl
+local label = "lint"
+local workers = 4
+local enabled = true
+local extensions = List("*.js", "*.ts")
+```
-pkl itself is also young and being improved so I am optimistic they may add some syntax sugar that would address some of these problems—or
-at least what I see as problems.
+Use `local` for helper values that are not part of hk’s schema. Without it, Pkl treats the value as a configuration property.
-## Testing pkl config
+Strings use double quotes, booleans use `true` and `false`, and a list uses `List(...)`.
-While I strongly encourage setting up your editor with a pkl extension to view errors inside the editor, you can also use the pkl cli to evaluate pkl files which is a great way to see what pkl is outputting without needing to run it through hk:
+## Define a step
-```sh
-$ pkl eval hk.pkl
-hooks {
- ["pre-commit"] {
- fix = true
- steps {
- ["prelint"] {
- check = "mise run prelint"
- exclusive = true
- }
- }
- }
+```pkl
+local eslint = new Step {
+ glob = List("*.js", "*.ts")
+ check = "eslint {{files}}"
+ fix = "eslint --fix {{files}}"
}
```
-Especially if you're doing dynamic configuration things I would strongly recommend doing this.
+`new Step` creates an instance of the schema’s step class. `{{files}}` is an hk command template, expanded later when the step runs; it is not Pkl interpolation.
-## Basic syntax
+## Reuse steps in mappings
-While of course pkl provides a [full reference](https://pkl-lang.org/main/current/language-reference/index.html), here I'll just show the pkl
-concepts we use in hk.
-
-### Basic Types
+Hooks and steps are mappings keyed by name:
```pkl
-my_string = "hello"
-my_number = 1
-my_boolean = true
-list_of_strings = List("a", "b", "c")
-```
-
-### Mapping
-
-Mappings are key-value pairs:
+local linters = new Mapping {
+ ["eslint"] = Builtins.eslint
+ ["prettier"] = Builtins.prettier
+}
-```pkl
-my_mapping = new Mapping {
- ["key"] = "value"
+hooks {
+ ["check"] { steps = linters }
+ ["fix"] {
+ fix = true
+ steps = linters
+ }
}
```
-### Listings/Lists
+A mapping entry uses `["name"] = value`. Each name must be unique within the mapping.
-Lists are for basic ordered collections:
+You can add entries to a new mapping with a spread:
```pkl
-my_list = List("a", "b", "c")
+local extended = new Mapping {
+ ...linters
+ ["shellcheck"] = Builtins.shellcheck
+}
```
-Listings are for more complex ordered collections:
+## Amend a builtin
+
+Parentheses followed by an object body create a modified copy:
```pkl
-my_listing = new Listing {
- new Step {
- check = "make lint"
- }
- new Step {
- check = "make format"
+local linters = new Mapping {
+ ["prettier"] = (Builtins.prettier) {
+ glob = List("*.js", "*.ts")
+ exclude = List("**/generated/**")
}
}
```
-### Local variables
+Unspecified properties keep the builtin’s values. Assigning a new list replaces that property’s list; it does not automatically append to it.
-hk rejects top-level properties it doesn't expect, so use the `local` keyword for helper variables:
+## Use raw strings for commands
+
+Raw strings help when a command contains quotes or backslashes:
```pkl
-local my_step = new Step {
- check = "make lint"
+local json_check = new Step {
+ glob = "*.json"
+ check = #"jq -e '.' {{files}} >/dev/null"#
}
```
-### Classes
-
-You typically won't define your own class with an hk config, but you will instantiate the ones provided by [Config.pkl](https://github.com/jdx/hk/blob/main/pkl/Config.pkl):
+For longer commands, use a multiline raw string:
```pkl
-local my_step = new Step {
- check = "make lint"
+local test = new Step {
+ check = #"""
+ echo "Running tests"
+ mise run test
+ """#
}
```
-### Amending objects
+The closing delimiter determines indentation. Keep the body indented consistently.
-If you want to reuse a shared object but amend it with modifications, use this syntax:
+## Comments
```pkl
-local make_lint = new Step {
- check = "make lint"
-}
-local linters = new Mapping {
- ["make-lint-a"] = (make_lint) {
- dir = "proj_a"
- }
- ["make-lint-b"] = (make_lint) {
- dir = "proj_b"
- }
-}
+// A comment
+/* A multiline comment */
+/// A documentation comment
+local explanation = "Documentation comments describe the following declaration."
```
-Essentially this is the same as:
+## Share configuration across files
```pkl
-local linters = new Mapping {
- ["make-lint-a"] = new Step {
- check = "make lint"
- dir = "proj_a"
- }
- ["make-lint-b"] = new Step {
- check = "make lint"
- dir = "proj_b"
+amends "./hk.pkl"
+
+hooks {
+ ["check"] {
+ steps {
+ ["local-check"] {
+ check = "make local-check"
+ }
+ }
}
}
```
-### Comments
+This is a local amendment of an existing project configuration. Save it as `hk.local.pkl` and keep it out of version control. The selected file amends `hk.pkl`; hk does not independently merge those two project files. See [local overrides](/configuration#hk-local-pkl).
-```pkl
-// This is a comment
-/*
-This is a multi-line comment
-*/
-/// This is a doc comment (not used by hk at least today)
+## Validate and inspect
+
+```sh
+hk validate
+hk check --plan
```
-### Amends
+Validation evaluates the configuration without executing linter commands. A plan then shows how hk selects steps and files.
-Every `hk.pkl` should start with this line, which validates the config against hk's schema and provides the base classes:
+If the Pkl CLI is installed, inspect the evaluated module with:
-```pkl
-amends "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Config.pkl"
+```sh
+pkl eval --format json hk.pkl
```
-### Imports
+Use the [Pkl language reference](https://pkl-lang.org/main/current/language-reference/index.html) for features beyond these examples.
-Share code between files by importing:
+## Evaluators
-```pkl
-import "./extra.pkl"
-// do something with `extra`
+hk includes [pklr](https://github.com/jdx/pklr) and uses it by default. If a configuration uses a Pkl feature that pklr does not yet support, try the Pkl CLI backend:
-import "https://example.com/remote.pkl"
-// do something with `remote`
+```sh
+mise use -g pkl
+HK_PKL_BACKEND=pkl hk validate
```
+Set `HK_PKL_BACKEND=pkl` in the environment used by Git as well if your hooks need that backend.
+
## Caching
-hk caches the evaluated output of each `hk.pkl` file (including its local imports) until one of those files is modified. Avoid reading environment variables inside `hk.pkl`, since the cache is not invalidated when an environment variable changes. Set `HK_CACHE=0` to bypass the cache when debugging.
+The built-in evaluator persists downloaded packages and seeds the cache with the Pkl package matching the running hk version. Use [`HK_PKL_OFFLINE`](/environment_variables#hk-pkl-offline) to require cached or embedded packages without network access.
+
+Release builds cache evaluated configuration; debug builds disable this cache by default. When diagnosing an unexpected result after changing an import or evaluation input, bypass or clear the cache:
+
+```sh
+HK_CACHE=0 hk validate
+hk cache clear
+```
+
+Use hk’s runtime settings, profiles, and command environment where possible instead of making configuration depend on changing evaluation inputs.
diff --git a/docs/public/custom-linters.pkl b/docs/public/custom-linters.pkl
index 793b10a9e..20d1b5b4e 100644
--- a/docs/public/custom-linters.pkl
+++ b/docs/public/custom-linters.pkl
@@ -1,123 +1,48 @@
-/// Example configuration with custom linters and platform-specific commands
-/// * Shows how to define custom linters not in builtins
-/// * Demonstrates platform-specific commands
-/// * Uses conditions and workspace indicators
-/// * Shows test configuration
+/// A custom step using hk's whitespace utility, including check and fix tests.
amends "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Config.pkl"
import "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Builtins.pkl"
-local custom_linters = new Mapping {
- // Custom SQL formatter
- ["sql_formatter"] {
- glob = List("**/*.sql")
- exclude = List("**/migrations/**")
- check = "sql-formatter --check {{files}}"
- fix = "sql-formatter --write {{files}}"
- batch = true
- }
-
- // Platform-specific security scanner
- ["security_scan"] {
- check = new Script {
- linux = "security-scanner-linux --scan {{files}}"
- macos = "security-scanner-mac --scan {{files}}"
- windows = "security-scanner.exe /scan {{files}}"
- }
- // Only run if security config exists
- condition = "test -f .security-config.yml"
- // Run exclusively to avoid conflicts
- exclusive = true
- }
-
- // Custom workspace-based build tool
- ["custom_build"] {
- workspace_indicator = "build.toml"
- // `dir` is templated, so commands run in each matched workspace
- dir = "{{workspace}}"
- check = "custom-build check"
- fix = "custom-build fix"
- // Use check_diff for efficient patching
- check_diff = "custom-build diff"
- }
-
- // Interactive migration tool
- ["migrate"] {
- glob = List("**/migrations/*.sql")
- check = "migrate validate {{files}}"
- fix = "migrate apply {{files}}"
- // Enable interactive mode for prompts
- interactive = true
- }
-
- // Custom linter with tests
- ["custom_validator"] {
- glob = List("**/*.custom")
- check = "validator {{files}}"
- fix = "validator --fix {{files}}"
-
- // Define tests for this step
+local linters = new Mapping {
+ ["whitespace"] {
+ glob = List("*.txt", "*.md")
+ check = "hk util trailing-whitespace {{files}}"
+ fix = "hk util trailing-whitespace --fix {{files}}"
tests {
- ["validates correct syntax"] {
+ ["accepts clean text"] {
run = "check"
+ files = List("{{tmp}}/clean.txt")
write {
- ["{{tmp}}/test.custom"] = #"valid content"#
- }
- files = List("{{tmp}}/test.custom")
- expect {
- code = 0
+ ["{{tmp}}/clean.txt"] = "hello\n"
}
+ expect { code = 0 }
}
- ["fixes invalid syntax"] {
+ ["removes trailing spaces"] {
run = "fix"
+ files = List("{{tmp}}/dirty.txt")
write {
- ["{{tmp}}/broken.custom"] = #"broken content"#
+ ["{{tmp}}/dirty.txt"] = "hello \n"
}
- files = List("{{tmp}}/broken.custom")
expect {
files {
- ["{{tmp}}/broken.custom"] = #"broken content"#
+ ["{{tmp}}/dirty.txt"] = "hello\n"
}
}
}
}
}
-}
-
-// Import some builtins and mix with custom
-local all_linters = new Mapping {
- ...custom_linters
- ["prettier"] = Builtins.prettier
- ["shellcheck"] = Builtins.shellcheck
+ ["newlines"] = Builtins.newlines
}
hooks {
["pre-commit"] {
fix = true
- stash = "git" // Stash unstaged changes while fixers run
- steps = all_linters
+ stash = "git"
+ steps = linters
}
- ["check"] {
- steps = all_linters
- // Generate a report after checking
- report =
- #"""
- echo "Check completed at $(date)"
- echo "Results: $HK_REPORT_JSON" | jq '.'
- """#
+ ["check"] { steps = linters }
+ ["fix"] {
+ fix = true
+ steps = linters
}
}
-
-// Show additional skip reasons for debugging
-display_skip_reasons =
- List(
- "profile-not-enabled",
- "no-files-to-process",
- "condition-false",
- )
-
-// Environment variables for all steps
-env {
- ["CUSTOM_VALIDATOR_STRICT"] = "true"
- ["SQL_FORMATTER_CONFIG"] = ".sql-format.yml"
-}
diff --git a/docs/public/javascript-project.pkl b/docs/public/javascript-project.pkl
index b5d47889d..869cf4338 100644
--- a/docs/public/javascript-project.pkl
+++ b/docs/public/javascript-project.pkl
@@ -1,49 +1,26 @@
-/// Example configuration for a JavaScript/TypeScript project
-/// * Uses prettier for formatting
-/// * Uses eslint for linting
-/// * Runs type checking with tsc
-/// * Enables automatic fixes in pre-commit
+/// Prettier and ESLint, with TypeScript checking enabled by the types profile.
amends "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Config.pkl"
import "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Builtins.pkl"
-// Configure environment for all tools
-env {
- ["NODE_ENV"] = "development"
-}
-
-// Define linters to use across hooks
local linters = new Mapping {
+ ["eslint"] = Builtins.eslint
["prettier"] = (Builtins.prettier) {
- // Enable batch processing for performance
- batch = true
- // Run prettier after other formatters
- depends = List("eslint")
- }
- ["eslint"] = (Builtins.eslint) {
- batch = true
+ // Choose a stable order when both tools can format JavaScript.
+ depends = "eslint"
}
["tsc"] = (Builtins.tsc) {
- // Type checking doesn't need file locking
- stomp = true
+ profiles = List("types")
}
}
hooks {
["pre-commit"] {
- // Enable automatic fixes
fix = true
- // Stash unstaged changes
stash = "git"
steps = linters
}
- ["pre-push"] {
- // Just check, don't fix
- steps = linters
- }
- ["check"] {
- steps = linters
- }
+ ["check"] { steps = linters }
["fix"] {
fix = true
steps = linters
diff --git a/docs/public/llms.txt b/docs/public/llms.txt
index d8a016294..f4321ee74 100644
--- a/docs/public/llms.txt
+++ b/docs/public/llms.txt
@@ -2,30 +2,38 @@
> Run linters concurrently without letting overlapping fixes race
-## Getting Started
+## Start here
-- [Getting Started](https://hk.jdx.dev/getting_started.html): This guide takes you from installing hk to running your first checks. Most projects can be set up in a few minutes.
-
-## Configuration
-
-- [Configuration](https://hk.jdx.dev/configuration.html): hk builds its effective configuration by layering sources from lowest to highest precedence.
+- [Getting started](https://hk.jdx.dev/getting_started.html): Set up hk in an existing Git repository, then use the same linters when you commit, work locally, and run CI.
+- [Why hk?](https://hk.jdx.dev/why-hk.html): hk is designed for projects that run several linters and formatters over overlapping files. It combines concurrent execution with file-level coordination, so tools can work together without writing to…
+- [Pkl essentials](https://hk.jdx.dev/pkl_introduction.html): hk uses Pkl for typed configuration. Most projects need only a few features: amend the schema, import builtins, define steps, and reuse them across hooks.
## Guides
-- [Built-in Linters](https://hk.jdx.dev/builtins.html): hk provides 150+ pre-configured linters and formatters through the Builtins module. Each builtin supplies the command, file matching, batching, and other hk behavior; the corresponding tool must be…
-- [Configuration Examples](https://hk.jdx.dev/reference/examples/): This directory contains runnable examples extracted from the public Pkl configurations.
-- [Git Hooks](https://hk.jdx.dev/hooks.html): This page describes the behavior of the git hooks that hk supports. Each step provides a "check" and a "fix" command. "check" commands are read-only and can run in parallel.
-- [mise Integration](https://hk.jdx.dev/mise_integration.html): Many git hook managers build in features that hk's sister project, mise-en-place, already provides. hk leaves those to mise, so use mise and hk together if you want any of the features described…
-- [Coding Agents](https://hk.jdx.dev/agents.html): hk gives coding agents a narrow, inspectable way to run project checks and fixes. Agents can use structured CLI output everywhere, or the MCP server for persistent runs, cancellation, paged logs,…
+- [Git hooks and stashing](https://hk.jdx.dev/hooks.html): A hook is a named collection of steps. Git invokes installed hooks at specific events; hk run invokes them directly. The check and fix hooks are also available through hk check and hk fix.
+- [Continuous integration](https://hk.jdx.dev/ci.html): Use hk check --all to run the project’s checks against a checkout. CI must install hk and every tool used by the configured steps, just as a developer’s machine does.
+- [mise integration](https://hk.jdx.dev/mise_integration.html): mise manages tools, environments, and tasks. hk selects files and coordinates checks and fixes. Together they let a team share tool versions and run hooks from terminals, editors, and CI.
+- [Troubleshooting](https://hk.jdx.dev/logging.html): Start with a plan and verbose output. They usually show whether the problem is file selection, configuration, or a linter command.
+- [Coding agents](https://hk.jdx.dev/agents.html): hk gives coding agents a narrow, inspectable way to run project checks and fixes. Agents can use structured CLI output everywhere, or the MCP server for persistent runs, cancellation, paged logs,…
+- [Configuration examples](https://hk.jdx.dev/reference/examples/): Choose a starting point, save its downloadable Pkl file as hk.pkl, and adapt the tools and paths to your project. Each page includes the exact file it offers for download.
+- [JavaScript and TypeScript](https://hk.jdx.dev/reference/examples/javascript-project.html): Run ESLint before Prettier, and enable TypeScript checking when you need it.
+- [Python](https://hk.jdx.dev/reference/examples/python-project.html): Use Ruff for linting and formatting, with mypy behind the types profile.
+- [Monorepo](https://hk.jdx.dev/reference/examples/monorepo.html): Organize frontend, backend, and infrastructure checks into groups, then add repository-wide Markdown and YAML checks.
+- [Custom steps](https://hk.jdx.dev/reference/examples/custom-linters.html): A step can invoke any shell command. Define which files it uses, how to check them without writes, and how to apply fixes.
-## CLI Reference
+## Reference
-- [CLI Reference](https://hk.jdx.dev/cli/): Usage: hk [FLAGS]
+- [Configuration](https://hk.jdx.dev/configuration.html): hk reads hk.pkl to decide which steps to run and how to run them. Start with a shared set of linters, then add file filters, dependencies, and profiles as your project needs them.
+- [Built-in linters](https://hk.jdx.dev/builtins.html): Builtins are reusable Pkl step definitions for linters, formatters, and hk’s own utilities. They supply file patterns, check and fix commands, and optimizations such as diff output.
+- [Environment variables](https://hk.jdx.dev/environment_variables.html): Use environment variables for a single invocation or an environment-wide preference. Runtime settings generally override Git and Pkl settings; CLI flags take precedence. See configuration precedence.
+- [Glossary](https://hk.jdx.dev/glossary.html): A reusable Pkl step definition supplied by hk. It describes how to invoke a linter or utility; external linter executables must be installed separately. Browse builtins.
+- [CLI commands](https://hk.jdx.dev/cli/): Run the same configured steps locally, in Git hooks, and in CI.
- [agent](https://hk.jdx.dev/cli/agent.html): Generate integration snippets for coding agents
- [agent hooks](https://hk.jdx.dev/cli/agent/hooks.html): Print a hook configuration for an agent or editor
- [agent instructions](https://hk.jdx.dev/cli/agent/instructions.html): Print project instructions for a coding agent
- [agent mcp](https://hk.jdx.dev/cli/agent/mcp.html): Print an MCP server configuration
- [builtins](https://hk.jdx.dev/cli/builtins.html): List all available builtin linters
+- [cache clear](https://hk.jdx.dev/cli/cache/clear.html): Clear the cache directory
- [check](https://hk.jdx.dev/cli/check.html): Run the check hook
- [completion](https://hk.jdx.dev/cli/completion.html): Generate shell completion scripts
- [config](https://hk.jdx.dev/cli/config.html): Inspect hk's configuration
@@ -72,17 +80,9 @@
- [validate](https://hk.jdx.dev/cli/validate.html): Validate the config file
- [version](https://hk.jdx.dev/cli/version.html): Print the version of hk
-## Reference
-
-- [Environment Variables](https://hk.jdx.dev/environment_variables.html): Environment variables can be used to configure hk.
-- [Pkl Introduction](https://hk.jdx.dev/pkl_introduction.html): hk uses pkl for configuration. As pkl is a relatively new configuration language, this doc gives an overview of how to write it and work with it for hk configuration.
-- [Logging and Debugging](https://hk.jdx.dev/logging.html): hk provides several ways to control logging output for debugging issues and understanding what happens during execution.
-- [Glossary](https://hk.jdx.dev/glossary.html): This glossary defines key terms used throughout hk documentation and configuration.
-
## Project
-- [Why hk?](https://hk.jdx.dev/why-hk.html): Tools like pre-commit, prek, and lefthook simply shell out to run linters. That means they can't safely run linters in parallel: if two linters try to modify the same file at the same time, there will…
-- [Benchmarks](https://hk.jdx.dev/benchmarks.html): These benchmarks compare hk, lefthook, pre-commit, and prek running 10 linters on a synthetic project.
-- [Contributing](https://hk.jdx.dev/contributing.html): Thank you for your interest in contributing to hk! This guide will help you get started.
-- [About](https://hk.jdx.dev/about.html): hk is built by @jdx.
-- [Sea Shanty](https://hk.jdx.dev/shanty.html): A sea shanty celebrating the hk git hook manager
+- [Benchmarks](https://hk.jdx.dev/benchmarks.html): These results measure one synthetic workload with overlapping linter file patterns. They illustrate how orchestration affects this setup; they are not a prediction for every repository or a survey of…
+- [About hk](https://hk.jdx.dev/about.html): hk is a Git hook manager and project linting tool built by @jdx. It is written in Rust and released under the MIT license.
+- [Contributing](https://hk.jdx.dev/contributing.html): Bug fixes, documentation improvements, and builtin definitions are welcome. For a substantial feature or behavior change, discuss the direction before investing in implementation.
+- [Sea shanty](https://hk.jdx.dev/shanty.html): A sea shanty celebrating the hk git hook manager
diff --git a/docs/public/monorepo.pkl b/docs/public/monorepo.pkl
index 0a89051de..046b71c93 100644
--- a/docs/public/monorepo.pkl
+++ b/docs/public/monorepo.pkl
@@ -1,56 +1,58 @@
/// Example configuration for a monorepo with multiple languages
-/// * Frontend: JavaScript/TypeScript with React
-/// * Backend: Rust
-/// * Infrastructure: Terraform
-/// * Uses groups to organize steps by component
+/// Frontend: JavaScript/TypeScript with React
+/// Backend: Rust
+/// Infrastructure: Terraform
+/// Uses groups to organize steps by component
amends "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Config.pkl"
import "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Builtins.pkl"
// Frontend linters (JavaScript/TypeScript)
local frontend = new Group {
+ // Inherited by frontend steps unless a child overrides `dir`.
+ dir = "frontend"
steps {
["prettier"] = (Builtins.prettier) {
- dir = "frontend"
batch = true
}
["eslint"] = (Builtins.eslint) {
- dir = "frontend"
batch = true
}
["stylelint"] = (Builtins.stylelint) {
- glob = List("frontend/**/*.css", "frontend/**/*.scss")
+ // Override the group dir for a step that scans files from the repo root.
+ dir = "."
+ glob = List("frontend/**/*.css", "frontend/**/*.scss", "packages/design-system/**/*.scss")
}
}
}
// Backend linters (Rust)
local backend = new Group {
+ // Inherited by all backend steps.
+ dir = "backend"
+ workspace_indicator = "Cargo.toml"
steps {
- ["cargo_fmt"] = (Builtins.cargo_fmt) {
- workspace_indicator = "Cargo.toml"
- dir = "backend"
- }
- ["cargo_clippy"] = (Builtins.cargo_clippy) {
- workspace_indicator = "Cargo.toml"
- dir = "backend"
- }
+ ["cargo_fmt"] = Builtins.cargo_fmt
+ ["cargo_clippy"] = Builtins.cargo_clippy
["cargo_check"] = (Builtins.cargo_check) {
- dir = "backend"
- // Only run in CI or with "full" profile
- profiles = List("ci", "full")
+ // Enable explicitly with --profile slow.
+ profiles = List("slow")
}
}
}
// Infrastructure linters (Terraform)
local infrastructure = new Group {
+ dir = "infrastructure"
+ exclude = List("**/.terraform/**")
steps {
["terraform"] = (Builtins.terraform) {
- glob = List("infrastructure/**/*.tf")
+ glob = "**/*.tf"
}
["tflint"] = (Builtins.tf_lint) {
- glob = List("infrastructure/**/*.tf")
+ glob = "**/*.tf"
+ // Child exclude replaces the group exclude, so repeat common exclusions.
+ exclude = List("**/.terraform/**", "modules/vendor/**")
}
}
}
diff --git a/docs/public/python-project.pkl b/docs/public/python-project.pkl
index ad3cc8387..6fe01d8c5 100644
--- a/docs/public/python-project.pkl
+++ b/docs/public/python-project.pkl
@@ -1,42 +1,15 @@
-/// Example configuration for a Python project
-/// * Uses ruff for fast linting
-/// * Uses ruff_format for fast formatting
-/// * Uses mypy for type checking
-/// * Sorts imports with isort
-/// * Validates with flake8
+/// Ruff linting and formatting, with optional mypy checks.
amends "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Config.pkl"
import "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Builtins.pkl"
-local python_linters = new Mapping {
- // Ruff is a fast Python linter
- ["ruff"] = (Builtins.ruff) {
- // Run ruff first as it's the fastest
- batch = true
+local linters = new Mapping {
+ ["ruff"] = Builtins.ruff
+ ["ruff-format"] = (Builtins.ruff_format) {
+ depends = "ruff"
}
- // Ruff formatter for code formatting
- ["ruff_format"] = (Builtins.ruff_format) {
- depends = "ruff" // Run after ruff
- }
- // Black for consistent formatting (alternative to ruff_format)
- ["black"] = (Builtins.black) {
- depends = "ruff_format" // Run after ruff_format
- }
- // isort for import sorting
- ["isort"] = (Builtins.isort) {
- depends = "black" // Run after black
- }
- // Type checking with mypy
["mypy"] = (Builtins.mypy) {
- // Type checking doesn't modify files
- stomp = true
- // Only run with "types" profile
- profiles = List("types", "full")
- }
- // Additional validation with flake8
- ["flake8"] = (Builtins.flake8) {
- // Only run in CI
- profiles = List("ci")
+ profiles = List("types")
}
}
@@ -44,23 +17,11 @@ hooks {
["pre-commit"] {
fix = true
stash = "git"
- steps = python_linters
- }
- ["pre-push"] {
- // Include type checking on push
- steps = python_linters
- env {
- ["HK_PROFILES"] = "types"
- }
- }
- ["check"] {
- steps = python_linters
+ steps = linters
}
+ ["check"] { steps = linters }
["fix"] {
fix = true
- steps = python_linters
+ steps = linters
}
}
-
-// Enable fail-fast for quicker feedback
-fail_fast = true
diff --git a/docs/reference/examples/custom-linters.md b/docs/reference/examples/custom-linters.md
index 3ae2cd022..cd8024cd5 100644
--- a/docs/reference/examples/custom-linters.md
+++ b/docs/reference/examples/custom-linters.md
@@ -1,134 +1,60 @@
-# Example: custom-linters
+---
+description: Define your own check and fix commands, test them, and add conditions or platform-specific scripts.
+---
+
+# Custom steps
+
+A step can invoke any shell command. Define which files it uses, how to check them without writes, and how to apply fixes.
+
+This example manually defines a whitespace step using an hk utility. It needs only hk, and includes tests you can run before adding the step to your workflow.
+
+Download custom-linters.pkl and save it as `hk.pkl`.
+
+## Configuration
+
+<<< @/public/custom-linters.pkl
+
+## Test the step
+
+```sh
+hk validate
+hk test --step whitespace
+hk check --all --plan
+```
+
+Each test writes a file in a temporary sandbox. One expects a clean check to succeed; the other checks the exact content after fixing. The `files` list explicitly selects the sandbox paths passed to each command.
+
+Use this pattern when adding a custom linter or contributing a builtin.
+
+## Add a condition
+
+Conditions use expression syntax. To invoke a shell test, wrap it in `exec`:
```pkl
-/// Example configuration with custom linters and platform-specific commands
-/// * Shows how to define custom linters not in builtins
-/// * Demonstrates platform-specific commands
-/// * Uses conditions and workspace indicators
-/// * Shows test configuration
-amends "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Config.pkl"
-import "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Builtins.pkl"
-
-local custom_linters = new Mapping {
- // Custom SQL formatter
- ["sql_formatter"] {
- glob = List("**/*.sql")
- exclude = List("**/migrations/**")
- check = "sql-formatter --check {{files}}"
- fix = "sql-formatter --write {{files}}"
- batch = true
- }
-
- // Platform-specific security scanner
- ["security_scan"] {
- check = new Script {
- linux = "security-scanner-linux --scan {{files}}"
- macos = "security-scanner-mac --scan {{files}}"
- windows = "security-scanner.exe /scan {{files}}"
- }
- // Only run if security config exists
- condition = "test -f .security-config.yml"
- // Run exclusively to avoid conflicts
- exclusive = true
- }
-
- // Custom workspace-based build tool
- ["custom_build"] {
- workspace_indicator = "build.toml"
- // `dir` is templated, so commands run in each matched workspace
- dir = "{{workspace}}"
- check = "custom-build check"
- fix = "custom-build fix"
- // Use check_diff for efficient patching
- check_diff = "custom-build diff"
- }
-
- // Interactive migration tool
- ["migrate"] {
- glob = List("**/migrations/*.sql")
- check = "migrate validate {{files}}"
- fix = "migrate apply {{files}}"
- // Enable interactive mode for prompts
- interactive = true
- }
-
- // Custom linter with tests
- ["custom_validator"] {
- glob = List("**/*.custom")
- check = "validator {{files}}"
- fix = "validator --fix {{files}}"
-
- // Define tests for this step
- tests {
- ["validates correct syntax"] {
- run = "check"
- write {
- ["{{tmp}}/test.custom"] = #"valid content"#
- }
- files = List("{{tmp}}/test.custom")
- expect {
- code = 0
- }
- }
- ["fixes invalid syntax"] {
- run = "fix"
- write {
- ["{{tmp}}/broken.custom"] = #"broken content"#
- }
- files = List("{{tmp}}/broken.custom")
- expect {
- files {
- ["{{tmp}}/broken.custom"] = #"broken content"#
- }
- }
- }
- }
- }
-}
+condition = "exec('test -f .lint-enabled')"
+```
-// Import some builtins and mix with custom
-local all_linters = new Mapping {
- ...custom_linters
- ["prettier"] = Builtins.prettier
- ["shellcheck"] = Builtins.shellcheck
-}
+This fragment assumes a POSIX shell. `condition` is evaluated for each job; use `step_condition` to evaluate once for the step.
-hooks {
- ["pre-commit"] {
- fix = true
- stash = "git" // Stash unstaged changes while fixers run
- steps = all_linters
- }
- ["check"] {
- steps = all_linters
- // Generate a report after checking
- report =
- #"""
- echo "Check completed at $(date)"
- echo "Results: $HK_REPORT_JSON" | jq '.'
- """#
- }
-}
+## Use platform-specific commands
+
+For a project that provides both shell and PowerShell check scripts, define a `Script`:
-// Show additional skip reasons for debugging
-display_skip_reasons =
- List(
- "profile-not-enabled",
- "no-files-to-process",
- "condition-false",
- )
-
-// Environment variables for all steps
-env {
- ["CUSTOM_VALIDATOR_STRICT"] = "true"
- ["SQL_FORMATTER_CONFIG"] = ".sql-format.yml"
+```pkl
+check = new Script {
+ linux = "sh scripts/check.sh"
+ macos = "sh scripts/check.sh"
+ windows = "pwsh -NoProfile -File scripts/check.ps1"
}
```
-## Description
+These scripts are project-owned placeholders: create them before using the fragment. They must leave files unchanged when used as a check.
+
+## Add optimizations when supported
+
+- `check_list_files` reports only the files that need fixing.
+- `check_diff` emits a unified diff that hk can apply.
+- `batch = true` lets hk divide files among jobs when the tool supports independent subsets.
+- `workspace_indicator` runs commands for matching projects.
-Example configuration with custom linters and platform-specific commands
-* Shows how to define custom linters not in builtins
-* Demonstrates platform-specific commands
-* Uses conditions and workspace indicators
-* Shows test configuration
+Keep the step’s selected files consistent with everything the command can modify. For broader effects, use dependencies or `exclusive = true`. See the [configuration reference](/configuration).
diff --git a/docs/reference/examples/index.md b/docs/reference/examples/index.md
index f341ade2d..df28c3d87 100644
--- a/docs/reference/examples/index.md
+++ b/docs/reference/examples/index.md
@@ -1,10 +1,24 @@
-# Configuration Examples
+---
+description: Complete hk configurations for JavaScript, Python, monorepos, and custom steps.
+---
-This directory contains runnable examples extracted from the public Pkl configurations.
+# Configuration examples
-## Available Examples
+Choose a starting point, save its downloadable Pkl file as `hk.pkl`, and adapt the tools and paths to your project. Each page includes the exact file it offers for download.
-- [custom-linters](./custom-linters.md)
-- [javascript-project](./javascript-project.md)
-- [monorepo](./monorepo.md)
-- [python-project](./python-project.md)
+| Example | Tools and concepts |
+| ------------------------------------------------- | ------------------------------------------------------------- |
+| [JavaScript and TypeScript](./javascript-project) | ESLint, Prettier, and optional TypeScript checking |
+| [Python](./python-project) | Ruff linting and formatting, plus optional mypy |
+| [Monorepo](./monorepo) | Component groups, inherited defaults, and multiple toolchains |
+| [Custom steps](./custom-linters) | Check/fix commands and self-contained step tests |
+
+Install any external tools the example invokes. Then validate and inspect the configuration before installing hooks:
+
+```sh
+hk validate
+hk check --all --plan
+hk install
+```
+
+For package environments and editor-launched hooks, see [mise integration](/mise_integration). For individual properties, see [configuration](/configuration).
diff --git a/docs/reference/examples/javascript-project.md b/docs/reference/examples/javascript-project.md
index e72e6f013..3e282116a 100644
--- a/docs/reference/examples/javascript-project.md
+++ b/docs/reference/examples/javascript-project.md
@@ -1,62 +1,33 @@
-# Example: javascript-project
-
-```pkl
-/// Example configuration for a JavaScript/TypeScript project
-/// * Uses prettier for formatting
-/// * Uses eslint for linting
-/// * Runs type checking with tsc
-/// * Enables automatic fixes in pre-commit
-amends "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Config.pkl"
-import "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Builtins.pkl"
-
-// Configure environment for all tools
-env {
- ["NODE_ENV"] = "development"
-}
-
-// Define linters to use across hooks
-local linters = new Mapping {
- ["prettier"] = (Builtins.prettier) {
- // Enable batch processing for performance
- batch = true
- // Run prettier after other formatters
- depends = List("eslint")
- }
- ["eslint"] = (Builtins.eslint) {
- batch = true
- }
- ["tsc"] = (Builtins.tsc) {
- // Type checking doesn't need file locking
- stomp = true
- }
-}
-
-hooks {
- ["pre-commit"] {
- // Enable automatic fixes
- fix = true
- // Stash unstaged changes
- stash = "git"
- steps = linters
- }
- ["pre-push"] {
- // Just check, don't fix
- steps = linters
- }
- ["check"] {
- steps = linters
- }
- ["fix"] {
- fix = true
- steps = linters
- }
-}
+---
+description: Configure ESLint, Prettier, and optional TypeScript checking with hk.
+---
+
+# JavaScript and TypeScript
+
+Run ESLint before Prettier, and enable TypeScript checking when you need it.
+
+**Prerequisites:** ESLint, Prettier, and TypeScript executables on `PATH`, plus their project configuration. If they are package dependencies, expose `node_modules/.bin` through [mise](/mise_integration#install-tools) or your existing environment.
+
+Download javascript-project.pkl and save it as `hk.pkl`.
+
+## Configuration
+
+<<< @/public/javascript-project.pkl
+
+## Try it
+
+```sh
+hk validate
+hk check --all --plan
+hk check --all
+hk check --all --profile types
+hk fix
```
-## Description
+ESLint and Prettier can both change JavaScript files. The dependency gives them a stable order; configure their rules to agree. TypeScript checking stays behind the `types` profile so it is opt-in.
+
+The same linter mapping powers `pre-commit`, `check`, and `fix`. The pre-commit hook saves unstaged work before fixing the staged versions.
+
+## Adapt it
-Example configuration for a JavaScript/TypeScript project
-* Uses prettier for formatting
-* Uses eslint for linting
-* Runs type checking with tsc
-* Enables automatic fixes in pre-commit
+Remove `tsc` for a JavaScript-only project. If your tools live in multiple packages, use [workspaces](/configuration#workspaces) or the [monorepo example](./monorepo). Use `hk check --all --profile types` in CI to include type checking.
diff --git a/docs/reference/examples/monorepo.md b/docs/reference/examples/monorepo.md
index d7203262e..9f4186184 100644
--- a/docs/reference/examples/monorepo.md
+++ b/docs/reference/examples/monorepo.md
@@ -1,110 +1,41 @@
-# Example: monorepo
+---
+description: Share step defaults across frontend, Rust, Terraform, and repository-wide checks.
+---
-This example shows a monorepo with frontend, backend, infrastructure, and shared steps.
+# Monorepo
-Groups can set common step attributes such as `dir`, `workspace_indicator`, `prefix`, `shell`, `stage`, and `exclude`. Child steps inherit those values by default, but a child can still set its own value when it needs different behavior. Child values replace group values; they are not merged.
+Organize frontend, backend, and infrastructure checks into groups, then add repository-wide Markdown and YAML checks.
-## Configuration
+**Prerequisites:** the tools referenced in the configuration must be available, with project configuration in the appropriate directories. This example expects `frontend/`, `backend/`, and `infrastructure/`.
-```pkl
-/// Example configuration for a monorepo with multiple languages
-/// * Frontend: JavaScript/TypeScript with React
-/// * Backend: Rust
-/// * Infrastructure: Terraform
-/// * Uses groups to organize steps by component
+Download monorepo.pkl and save it as `hk.pkl`.
-amends "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Config.pkl"
-import "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Builtins.pkl"
+## Configuration
-// Frontend linters (JavaScript/TypeScript)
-local frontend = new Group {
- // Inherited by frontend steps unless a child overrides `dir`.
- dir = "frontend"
- steps {
- ["prettier"] = (Builtins.prettier) {
- batch = true
- }
- ["eslint"] = (Builtins.eslint) {
- batch = true
- }
- ["stylelint"] = (Builtins.stylelint) {
- // Override the group dir for a step that scans files from the repo root.
- dir = "."
- glob = List("frontend/**/*.css", "frontend/**/*.scss", "packages/design-system/**/*.scss")
- }
- }
-}
+<<< @/public/monorepo.pkl
-// Backend linters (Rust)
-local backend = new Group {
- // Inherited by all backend steps.
- dir = "backend"
- workspace_indicator = "Cargo.toml"
- steps {
- ["cargo_fmt"] = Builtins.cargo_fmt
- ["cargo_clippy"] = Builtins.cargo_clippy
- ["cargo_check"] = (Builtins.cargo_check) {
- // Only run in CI or with "full" profile.
- profiles = List("ci", "full")
- }
- }
-}
+## Understand the boundaries
-// Infrastructure linters (Terraform)
-local infrastructure = new Group {
- dir = "infrastructure"
- exclude = List("**/.terraform/**")
- steps {
- ["terraform"] = (Builtins.terraform) {
- glob = "**/*.tf"
- }
- ["tflint"] = (Builtins.tf_lint) {
- glob = "**/*.tf"
- // Child exclude replaces the group exclude, so repeat common exclusions.
- exclude = List("**/.terraform/**", "modules/vendor/**")
- }
- }
-}
+Each group can provide common defaults such as `dir`, `prefix`, and `workspace_indicator`. A child keeps an explicitly defined property; child values replace rather than merge with group values. Builtins may already define these properties.
-// Shared linters (apply to all components)
-local shared = new Mapping {
- ["markdown"] = (Builtins.markdown_lint) {
- glob = List("**/*.md")
- exclude = List("**/node_modules/**", "**/target/**")
- }
- ["yaml"] = (Builtins.yamllint) {
- glob = List("**/*.yaml", "**/*.yml")
- exclude = List("**/node_modules/**")
- }
-}
+Groups also affect scheduling: children can run concurrently within a group, but groups run in order. If frontend and backend checks should overlap, place the steps in one mapping and use `depends` only where ordering is required.
-hooks {
- ["pre-commit"] {
- fix = true
- stash = "git"
- steps {
- ["frontend"] = frontend
- ["backend"] = backend
- ["infrastructure"] = infrastructure
- ...shared
- }
- }
- ["check"] {
- steps {
- ["frontend"] = frontend
- ["backend"] = backend
- ["infrastructure"] = infrastructure
- ...shared
- }
- }
-}
+## Try it
+
+```sh
+hk validate
+hk check --all --plan
+hk check --all
+hk check --all --profile slow
```
-## Key Features
+The `slow` profile enables the additional Cargo check. It is not enabled automatically in CI.
-- Group-level defaults keep shared settings close to the component they apply to.
-- Child steps can override inherited values when a tool needs a different working directory, glob, shell, stage, prefix, workspace indicator, or exclude list.
-- Override semantics are simple: a child value replaces the group value instead of merging with it.
+## Adapt it
+
+Change `dir` values to match your repository, remove components you do not use, and inspect `--plan` to verify how paths and workspaces are selected. Use `hk check --why ` when a component is unexpectedly skipped.
+
+For tools that discover nested packages, see [workspaces](/configuration#workspaces).
## Nested configs with `subprojects`
@@ -173,54 +104,5 @@ The matching mise configuration makes hk, aube, and each component's tools
available in the directory where its steps run:
```toml
-# mise.toml (repo root)
-monorepo_root = true
-
-[monorepo]
-config_roots = [".", "frontend", "backend"]
-
-[tools]
-aube = "latest"
-hk = "latest"
-[env]
-HK_MISE = 1
-
-[hooks]
-postinstall = "hk install --mise"
-```
-
-```toml
-# frontend/mise.toml
-[tools]
-node = "lts"
```
-
-```toml
-# backend/mise.toml
-[tools]
-rust = "stable"
-```
-
-When hk runs from the repo root, each subproject's hooks are merged in, scoped to
-its directory:
-
-- Step working directories and glob matching are relative to the subdirectory, so
- `frontend/hk.pkl` only sees files under `frontend/`.
-- Step names are prefixed with the directory (e.g. `frontend:eslint`), which is the
- name to use with `--step` or `skip_steps`.
-- A subproject's `env` applies to its own steps only.
-- Glob entries like `packages/*` match any directory containing an hk config file;
- directories without one are skipped.
-- Hooks compose by name. Steps declared only under `check` do not automatically run
- under `pre-commit` or `fix`.
-- Define hook-wide settings such as `fix`, `stash`, `stage`, and `report` in the root
- config so every subproject uses the same behavior.
-- Only one level of subprojects is supported.
-
-This maps directly onto [mise monorepo config roots](https://mise.jdx.dev/tasks/monorepo.html):
-the same directories that own a `mise.toml` can own their `hk.pkl`.
-
-Use `hk check --all --plan` to inspect the resolved jobs without executing them.
-For this example, the plan includes `frontend:eslint`, `frontend:prettier`,
-`backend:cargo-fmt`, and `backend:cargo-clippy`.
diff --git a/docs/reference/examples/python-project.md b/docs/reference/examples/python-project.md
index d02949e21..57219962d 100644
--- a/docs/reference/examples/python-project.md
+++ b/docs/reference/examples/python-project.md
@@ -1,78 +1,45 @@
-# Example: python-project
+---
+description: Configure Ruff checks and formatting with optional mypy type checking.
+---
-```pkl
-/// Example configuration for a Python project
-/// * Uses ruff for fast linting
-/// * Uses ruff_format for fast formatting
-/// * Uses mypy for type checking
-/// * Sorts imports with isort
-/// * Validates with flake8
-amends "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Config.pkl"
-import "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Builtins.pkl"
+# Python
-local python_linters = new Mapping {
- // Ruff is a fast Python linter
- ["ruff"] = (Builtins.ruff) {
- // Run ruff first as it's the fastest
- batch = true
- }
- // Ruff formatter for code formatting
- ["ruff_format"] = (Builtins.ruff_format) {
- depends = "ruff" // Run after ruff
- }
- // Black for consistent formatting (alternative to ruff_format)
- ["black"] = (Builtins.black) {
- depends = "ruff_format" // Run after ruff_format
- }
- // isort for import sorting
- ["isort"] = (Builtins.isort) {
- depends = "black" // Run after black
- }
- // Type checking with mypy
- ["mypy"] = (Builtins.mypy) {
- // Type checking doesn't modify files
- stomp = true
- // Only run with "types" profile
- profiles = List("types", "full")
- }
- // Additional validation with flake8
- ["flake8"] = (Builtins.flake8) {
- // Only run in CI
- profiles = List("ci")
- }
-}
+Use Ruff for linting and formatting, with mypy behind the `types` profile.
-hooks {
- ["pre-commit"] {
- fix = true
- stash = "git"
- steps = python_linters
- }
- ["pre-push"] {
- // Include type checking on push
- steps = python_linters
- env {
- ["HK_PROFILES"] = "types"
+**Prerequisites:** `ruff` and `mypy` on `PATH`, along with your project’s rules and type-checking configuration. Activate your virtual environment or use [mise](/mise_integration) to provide the tools.
+
+Download python-project.pkl and save it as `hk.pkl`.
+
+## Configuration
+
+<<< @/public/python-project.pkl
+
+## Try it
+
+```sh
+hk validate
+hk check --all --plan
+hk check --all
+hk check --all --profile types
+hk fix
+```
+
+Ruff’s formatter waits for Ruff’s lint fixes. mypy runs only when `types` is enabled. The profile must be enabled for the hk invocation; setting `HK_PROFILE` in a hook’s child-command environment does not select it.
+
+## Adapt it
+
+If you prefer Black, replace the `ruff-format` entry with `Builtins.black`. Choose one primary formatter to avoid conflicting formatting passes.
+
+For a push hook that always includes mypy, add a `pre-push` hook using an amended linter mapping and clear mypy’s profile requirement there:
+
+```pkl
+["pre-push"] {
+ steps = (linters) {
+ ["mypy"] = (Builtins.mypy) {
+ profiles = List()
}
}
- ["check"] {
- steps = python_linters
- }
- ["fix"] {
- fix = true
- steps = python_linters
- }
}
-
-// Enable fail-fast for quicker feedback
-fail_fast = true
```
-## Description
-
-Example configuration for a Python project
-* Uses ruff for fast linting
-* Uses ruff_format for fast formatting
-* Uses mypy for type checking
-* Sorts imports with isort
-* Validates with flake8
+Place this fragment inside `hooks`. Locally and in CI, `hk check --all --profile types` includes type checking without a separate hook.
diff --git a/docs/shanty.md b/docs/shanty.md
index c4c6acdaa..4f194ada3 100644
--- a/docs/shanty.md
+++ b/docs/shanty.md
@@ -1,11 +1,18 @@
+---
+description: Listen to Haul Away, HK, a sea shanty about Git hooks, Pkl, and mise.
+---
+
# Haul Away, HK
-*A sea shanty celebrating the hk git hook manager*
+_A sea shanty celebrating the hk git hook manager_
-