- 🦀 Easily set up in your Rust project. No need to install additional package managers.
- ⚙️ Works with custom
build.rsfiles. Automate the hooks installation process. - 💻 Run your hooks via CLI. Test your hooks without triggering them via Git.
Keep calm, monk will protect your repo!
You can install it using cargo:
cargo install monkYou can add it as a build dependency:
cargo add --build monkThen create a build.rs file:
pub fn main() {
monk::init();
}In this case, monk will be installed automatically and will initialize all hooks from monk.yaml
.
This is the most convenient option for Rust projects, as it doesn't require contributors to install monk manually.
You can also install monk using Nix:
nix profile install github:daynin/monkYou can install monk using GNU Guix directly from GitHub:
# Install latest version from main branch
guix package -f <(curl -s https://raw.githubusercontent.com/daynin/monk/main/monk.scm)Note: This will automatically fetch and build the latest version from the main branch.
Create a configuration file named monk.yaml (or monk.toml) in your project root:
pre-commit:
commands:
fmt:
run: cargo fmt -- --check
clippy:
run: cargo clippy -- -D warnings
pre-push:
commands:
test:
run: cargo testThen install the hooks:
monk installIf you added monk as a build dependency with build.rs (see above), hooks are installed automatically when you build your project.
Each command has a name and a run field:
pre-commit:
commands:
fmt:
run: cargo fmt -- --check
clippy:
run: cargo clippy -- -D warnings
test:
run: cargo testCommands run in the order they are defined. If any command fails, execution stops and the hook fails.
Legacy format (backward compatible)
Plain string arrays still work:
pre-commit:
commands:
- cargo fmt -- --check
- cargo clippy -- -D warningsCommands are auto-named cmd1, cmd2, etc. The named format is recommended for new configs.
Use placeholders to pass file lists to your tools:
| Placeholder | Expands to |
|---|---|
{staged_files} |
Files staged for commit (git diff --cached) |
{push_files} |
Files changed between local and remote |
{all_files} |
All tracked files in the repository |
pre-commit:
commands:
lint:
run: eslint {staged_files}
fmt:
run: prettier --write {staged_files}
pre-push:
commands:
test:
run: cargo test {push_files}When a placeholder expands to an empty file list, the command is automatically skipped. If the expanded command exceeds the OS argument length limit, it is automatically split into batches.
Use glob and exclude to filter which files a command applies to:
pre-commit:
commands:
lint-js:
run: eslint {staged_files}
glob: "*.{js,ts}"
exclude: "*.min.js"
lint-rs:
run: cargo clippy
glob: "*.rs"
fmt:
run: prettier --write {staged_files}
glob:
- "*.js"
- "*.ts"
- "*.css"Both glob and exclude accept a single pattern or a list of patterns. Patterns without a / match files in any directory (e.g., *.rs matches src/main.rs).
When glob is set but no files match, the command is skipped. When glob is used with a placeholder like {staged_files}, only matching files are passed to the command.
When glob is set without a file placeholder, monk checks staged files against the pattern and skips the command if none match.
For monorepos with multiple modules or mixed technologies:
pre-commit:
paths:
"frontend/":
commands:
lint:
run: npm run lint
test:
run: npm test
working_directory: frontend
"backend/":
commands:
fmt:
run: cargo fmt -- --check
clippy:
run: cargo clippy -- -D warnings
working_directory: backendWhen using monk run --changed-only (the default for installed hooks), only hooks whose path prefix matches the changed files will run.
Set working_directory at the hook level or the command level. Command-level overrides hook-level:
pre-commit:
commands:
frontend-lint:
run: npm run lint
working_directory: frontend
backend-test:
run: cargo test
working_directory: backendOr at the hook level for all commands:
pre-commit:
commands:
fmt:
run: cargo fmt -- --check
test:
run: cargo test
working_directory: backendRun all commands in a hook concurrently with parallel: true:
pre-commit:
parallel: true
commands:
fmt:
run: cargo fmt -- --check
clippy:
run: cargo clippy -- -D warnings
test:
run: cargo testAll commands run simultaneously and their output is buffered. A summary with pass/fail status and timing is printed after all commands finish. If any command fails, the hook fails.
Run commands sequentially in priority order with piped: true:
pre-commit:
piped: true
commands:
install:
run: npm install
priority: 1
lint:
run: eslint .
priority: 2
test:
run: npm test
priority: 3Commands are sorted by priority (lower number runs first). Commands without priority run after prioritized ones, in their original definition order. If any command fails, execution stops and the hook fails.
Add follow: true to continue running all commands even when one fails:
post-merge:
piped: true
follow: true
commands:
bundle:
run: bundle install
priority: 1
migrate:
run: bundle exec rails db:migrate
priority: 2With follow: true, all commands run regardless of failures and a summary with pass/fail status is printed at the end. If any command failed, the hook fails.
When both piped and parallel are set, piped takes precedence.
Skip hooks or individual commands based on git state, branch, or shell conditions:
pre-commit:
skip:
- merge
- rebase
commands:
fmt:
run: cargo fmt -- --check
slow-test:
run: cargo test --all
skip:
- run: "test -n \"$CI\""
pre-push:
commands:
deploy:
run: ./deploy.sh
skip:
- ref: main
test:
run: cargo test
skip:
- ref: "release/*"| Condition | Skips when |
|---|---|
merge |
A merge is in progress (.git/MERGE_HEAD exists) |
rebase |
A rebase is in progress |
ref: <pattern> |
Current branch matches the pattern (supports globs like release/*) |
run: <command> |
Shell command exits with code 0 |
Skip accepts a single condition or a list. If any condition matches, the hook or command is skipped.
To disable all hooks globally, set the environment variable MONK=0.
Set environment variables for specific commands using the env key:
pre-commit:
commands:
lint:
run: eslint {staged_files}
env:
NODE_ENV: production
FORCE_COLOR: "1"
test:
run: cargo test
env:
RUST_LOG: debugEnvironment variables are added to the command's process environment (they augment the inherited environment, not replace it). Each command can have its own set of environment variables.
Use the top-level rc key to source a shell script before every command:
rc: .monkrc
pre-commit:
commands:
lint:
run: eslint .
test:
run: npm testThe RC file is sourced via . <path> && <command> (POSIX-compatible dot-source). This is useful for shell-managed toolchains like nvm, rbenv, or pyenv that require shell initialization before tools are available.
The rc path is relative to the project root. RC also applies to skip: run: shell conditions.
Create a monk-local.yaml file (add it to .gitignore) to override or extend your project's monk.yaml without affecting teammates:
pre-commit:
parallel: true
commands:
clippy:
run: cargo clippy
mycheck:
run: ./my-local-check.sh
pre-push:
skip:
- ref: mainMerge rules:
- Hooks: merged by name. New hooks are added, existing hooks are deep-merged.
- Commands: merged by name. A local command with the same name fully replaces the base command. New commands are added.
- Scalar fields (
parallel,working_directory): local value overrides base. - Skip conditions: local replaces base (not concatenated).
- Different hook variants (Simple vs PathBased): local replaces base entirely.
If monk-local.yaml does not exist, monk.yaml is used as-is. Local configs also support TOML format (monk-local.toml). Main and local configs can use different formats.
Monk supports TOML as an alternative to YAML. Create a monk.toml file instead of monk.yaml:
[pre-commit]
parallel = true
[pre-commit.commands.fmt]
run = "cargo fmt -- --check"
glob = ["*.rs"]
[pre-commit.commands.clippy]
run = "cargo clippy -- -D warnings"
[pre-push.commands.test]
run = "cargo test"
skip = ["merge"]Monk searches for config files in this order: monk.yaml, monk.toml. The first one found is used. The same applies to local overrides: monk-local.yaml, monk-local.toml.
monk install # Install hooks defined in monk.yaml
monk run <hook> # Run a hook manually (e.g., monk run pre-commit)
monk uninstall # Remove hooks and restore backupsmonk automatically backs up existing hooks before installing. Running monk uninstall restores the original hooks.