- 🦀 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.
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.
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.