{"id":16661931,"url":"https://github.com/brandonchinn178/hooky","last_synced_at":"2026-04-01T18:18:02.668Z","repository":{"id":42508639,"uuid":"476162196","full_name":"brandonchinn178/hooky","owner":"brandonchinn178","description":"A minimal git pre-commit hook runner","archived":false,"fork":false,"pushed_at":"2026-03-19T04:50:00.000Z","size":1882,"stargazers_count":23,"open_issues_count":7,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-19T18:15:27.430Z","etag":null,"topics":["git","pre-commit","pre-commit-hooks"],"latest_commit_sha":null,"homepage":"","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brandonchinn178.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-03-31T05:23:28.000Z","updated_at":"2026-03-19T04:50:04.000Z","dependencies_parsed_at":"2026-01-03T19:00:42.213Z","dependency_job_id":null,"html_url":"https://github.com/brandonchinn178/hooky","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/brandonchinn178/hooky","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brandonchinn178%2Fhooky","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brandonchinn178%2Fhooky/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brandonchinn178%2Fhooky/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brandonchinn178%2Fhooky/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brandonchinn178","download_url":"https://codeload.github.com/brandonchinn178/hooky/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brandonchinn178%2Fhooky/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31290807,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["git","pre-commit","pre-commit-hooks"],"created_at":"2024-10-12T10:36:29.284Z","updated_at":"2026-04-01T18:18:02.652Z","avatar_url":"https://github.com/brandonchinn178.png","language":"Haskell","readme":"# Hooky\n\nMinimal git hooks manager.\n\nFeatures:\n* Only runs on staged files\n    * Supports partially staged and untracked files\n* Hooks run in parallel\n* Built-in support for auto-fixing files\n* Show stdout while hook is in-progress\n* Support for passing files to hooks via response files\n\n\u003cimg src=\"scripts/demo/demo.gif\" /\u003e\n\n## Installation\n\n### With Homebrew\n\n```shell\nbrew install brandonchinn178/tap/hooky\n```\n\n### GitHub release\n\nDownload the appropriate binary from the [GitHub releases page](https://github.com/brandonchinn178/hooky/releases).\n\n## Usage\n\n* `hooky install [--mode MODE] [--format FORMAT]`\n    * Registers `hooky` with git hooks.\n    * `--mode` determines what to do when hooks modify files:\n        * `check` (default) - Run the commands in `check` mode, which shouldn't modify files\n        * `fix` - Run the commands in `fix` mode, which may modify files\n        * `fix-add` - Same as `fix`, except add the changes to the stage and continue the commit\n    * `--format` determines how to format the output:\n        * `minimal` (default) - Only show full stdout of failed hooks.\n        * `full` - Show passed/skipped hooks, without their stdout. Include durations for all hooks.\n        * `verbose` - Show full stdout + duration of all hooks.\n    * Configure defaults with the [Global configuration](#global-configuration)\n\n* `hooky run [FILES ...] [-k HOOK]`\n    * Manually runs the given hooks in `check` mode\n    * Files may be specified as `@foo.txt`, where `foo.txt` contains one file per line.\n    * Shortcuts:\n        * `--modified`/`-m` = `@\u003c(git diff --name-only --diff-filter=AMR)`\n        * `--staged`/`-s` = `@\u003c(git diff --staged --name-only --diff-filter=AMR)`\n        * `--all`/`-a` = `@\u003c(git ls-files)`\n        * `--prev`/`-1` = `@\u003c(git diff HEAD~1..HEAD --name-only --diff-filter=AMR)`\n    * `--stash` stashes all unstaged changes before running\n    * If no files are specified, equivalent to `--stash --staged`\n    * `-k` may be specified multiple times, specifying the hooks to run\n\n* `hooky fix [FILES ...] [-k HOOK]`\n    * Same as `hooky run`, except runs in `fix` mode\n\n### `.hooky.kdl` configuration\n\nConfiguration is specified with the [KDL](https://kdl.dev) language.\n\nEach hook is specified in a `hook` section:\n\n```kdl\nhook name_of_hook {\n    command my_formatter arg0 arg1 {\n        check_args --mode check\n        fix_args --mode fix\n        pass_files file\n    }\n    files *.py *.txt\n}\n```\n\n* `command`: The command to run\n    * You're responsible for installing the necessary tools. [dotslash](https://dotslash-cli.com/docs) is a good way to automatically install binary tools.\n    * Can be written without quotes, but quotes are needed if special characters like `/` or spaces are used\n* `check_args`: Additional arguments to pass when running in `check` mode\n* `fix_args`: Additional arguments to pass when running in `fix` mode\n* `pass_files`: How to pass files to the command\n    * `xargs` (default) - Pass files to `xargs`, which will batch execute\n    * `xargs_parallel` - Same as `xargs`, except run in parallel\n    * `file` - Put files into a file (one file per line) and pass the list as an argument as `@path/to/file.txt`\n    * `none` - Don't pass files to the command at all\n* `files`: Globs of files that should trigger the hook\n    * `*` matches any character except `/`\n    * `**` matches zero or more directories\n      * e.g. `a/**/*.txt` matches `a/foo.txt` and `a/b/foo.txt` but not `b/foo.txt`\n    * Relative paths match anywhere in the repo\n      * e.g. `*.txt` matches `foo.txt` and `foo/bar.txt`\n      * In other words, `**/` is auto-prepended to the beginning\n    * Absolute paths match from the root of the repo\n      * e.g. `/*.txt` matches `foo.txt` but not `foo/bar.txt`\n    * A glob can be negated with a `!` at the beginning\n      * e.g. `!foo.txt` matches `bar.txt` but not `foo.txt` nor `a/foo.txt`\n      * e.g. `!/foo.txt` matches `bar.txt` and `a/foo.txt` but not `foo.txt`\n    * When multiple globs are specified, a file matches if:\n      * Any positive globs match, AND\n      * All negative globs don't match\n\nYou may also specify defaults in the `defaults` section. Available options are documented below:\n\n```kdl\ndefaults {\n    // Any globs specified here are inherited by all hooks\n    files *.py\n}\n```\n\n### Built-in hooks\n\nFor convenience, Hooky also ships with some general purpose hooks, which can be specified as:\n\n```kdl\nhook hooky {\n    command hooky lint {\n        fix_args --fix\n        pass_files file\n    }\n    files *\n}\n```\n\n`hooky lint` runs the rules specified under the `lint_rules` section, e.g.\n\n```kdl\nlint_rules {\n    - check_broken_symlinks\n    - no_commit_to_branch {\n        branches {\n            - main\n            - release-*\n        }\n    }\n    - trailing_whitespace {\n        files \"!foo.txt\"\n    }\n}\n```\n\nAll rules have the following configuration:\n\n* `files`: Same as `files` in `hook` sections. If not specified, defaults to `*`\n\nAvailable rules and rule-specific configuration:\n\n* `check_broken_symlinks` - Check if any symlinks which do not point to anything\n* `check_case_conflict` - Check if any files differ only by case, which would cause issues on case-insensitive filesystems like macOS\n* `check_merge_conflict` - Check if any files contain merge conflict strings\n* `end_of_file_fixer` - Ensure files end with exactly one newline\n* `no_commit_to_branch` - Prevent commits to specific branches\n    * `branches` - Branches to prevent commits to, as a glob pattern\n* `trailing_whitespace` - Remove trailing whitespace\n\n### Global configuration\n\nGlobal configuration can also be specified at `$XDG_CONFIG_DIR/hooky/settings.kdl` (e.g. `~/.config/hooky/settings.kdl`).\n\nAvailable configuration and their defaults is documented below:\n\n```kdl\nflags {\n    // The default --mode to use when committing, if not overridden in `hooky install`\n    --mode check\n\n    // The default --format to use when committing, if not overridden in `hooky install`\n    --format minimal\n}\n\n// The maximum number of output lines to show while a hook is running\nmax_output_lines 5\n\n// The maximum number of hooks to run in parallel in `check` mode\nmax_parallel_hooks 5\n```\n\n### Skipping hooks\n\nTo temporarily skip hooks, use the `SKIP` env var, which takes a comma-delimited list of hooks to skip. This works for both `hook`s and `lint_rules`.\n\n## Example hook configurations\n\n### Ruff in a uv project\n\n```kdl\nhook ruff_check {\n    command uv run ruff check {\n        fix_args --fix\n        pass_files file\n    }\n    files *.py\n}\n\nhook ruff_format {\n    command uv run ruff format {\n        check_args --check\n        pass_files file\n    }\n    files *.py\n}\n```\n\n### ESLint in an npm project\n\n```kdl\nhook eslint {\n    command npx eslint {\n        fix_args --fix\n        pass_files xargs\n    }\n    files *.json *.js *.jsx *.ts *.tsx\n}\n```\n\n### Uses of Hooky in the wild\n\nSee examples of Hooky configurations across GitHub: https://github.com/search?q=path%3A.hooky.kdl\u0026type=code\n\n## Comparison with other tools\n\n### pre-commit\n\n[`pre-commit`](https://pre-commit.com) is primarily a tool/environment manager ([ref](https://github.com/pre-commit/pre-commit/pull/3577), [ref2](https://github.com/pre-commit/pre-commit/issues/2316#issuecomment-1083643390)), not a command runner. IMO this is the wrong direction:\n* `pre-commit` doesn't have any lockfiles, whereas your language's normal dependency management will likely support pinned dependencies\n* With `pre-commit`, you have to re-configure the linter in `.pre-commit-config.yaml`, e.g. re-specifying `eslint` plugins\n\nAt the end of the day, git pre-commit hooks should just be a matter of taking commands you can already run manually and registering them with git hooks. That is the only thing Hooky cares about, and how you want to manage your tools is up to you.\n\nOther notable differences:\n* `hooky` installs as a compiled executable; no need to have the right Python environment\n* Built-in functionality for fixing failures, with optional auto-add fixes in commit\n* Hooks run in parallel\n* Show hook output while it's running, to get more visibility into what's happening\n* Support passing files via response files instead of using `xargs`\n\n### husky\n\n[Husky](https://typicode.github.io/husky/#/) is pretty specific to NodeJS projects. It's implemented in Javascript, so you need to install Husky with `npm`. By default, it will run the `prepare` script in `package.json`, but you can also add arbitrary commands to `.husky/pre-commit`. This is flexible, but completely open-ended, with no structure running/configuring each hook independently.\n\n### prek\n\n[prek](https://prek.j178.dev/) is essentially a reimplementation of [`pre-commit`](https://pre-commit.com), so it inherits the same issues.\n\n### lefthook\n\n[lefthook](https://lefthook.dev) has a similar philosophy as Hooky around only specifying commands to run.\n\nThe biggest difference between Hooky and lefthook is Hooky natively distinguishes between check and fix modes. You commit to the repo how a given linter checks and fixes, and each developer decides for themselves whether they want the git hook to only check without modification, or to also autofix. Hooky will fail to commit if anything is fixed (unless you run in the `fix-add` mode) and displays a message on adding the changes to your commit. Without `stage_fixed`, lefthook will simply leave the changes on disk and continue the commit.\n\nIn general, Hooky also aims to be much more minimal:\n* lefthook always shows the output of the command that was run, whereas Hooky only shows the output for failed commands by default\n* lefthook's configuration is much more complex. It also supports configuring general tasks, whereas Hooky focuses only on managing git hook commands. Your project probably already has general task runners like `Makefile` or `pyproject.toml`/`package.json` scripts.\n\nOther notable differences:\n* Hooky comes with some useful linters out-of-the-box like checking trailing whitespace, for convenience.\n* Hooky stashes partially staged changes, which ensures that when a linter runs on a file, it will see exactly the content that's going into the commit and not lint unstaged changes.\n* Hooky shows the output of a linter while it's running, so users have visibility into what's taking a long time. lefthook only shows output at the end.\n* Hooky supports passing the modified files to the command as a response file (`@files.txt`), so that a long list of files still only invokes the command once, and the command can read all the files from there (if it supports it).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrandonchinn178%2Fhooky","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrandonchinn178%2Fhooky","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrandonchinn178%2Fhooky/lists"}