{"id":49596852,"url":"https://github.com/zacsweers/kempt","last_synced_at":"2026-06-13T05:01:28.570Z","repository":{"id":355522127,"uuid":"1227529033","full_name":"ZacSweers/kempt","owner":"ZacSweers","description":"A pre-commit-friendly multi-language source formatter","archived":false,"fork":false,"pushed_at":"2026-05-04T04:53:44.000Z","size":101,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-04T05:06:18.090Z","etag":null,"topics":["formatting","google-java-format","kotlin","ktfmt"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ZacSweers.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","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":"2026-05-02T20:11:45.000Z","updated_at":"2026-05-04T04:53:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ZacSweers/kempt","commit_stats":null,"previous_names":["zacsweers/kempt"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/ZacSweers/kempt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZacSweers%2Fkempt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZacSweers%2Fkempt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZacSweers%2Fkempt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZacSweers%2Fkempt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ZacSweers","download_url":"https://codeload.github.com/ZacSweers/kempt/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZacSweers%2Fkempt/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34272603,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-13T02:00:06.617Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["formatting","google-java-format","kotlin","ktfmt"],"created_at":"2026-05-04T05:02:59.943Z","updated_at":"2026-06-13T05:01:28.563Z","avatar_url":"https://github.com/ZacSweers.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# kempt\n\nA pre-commit-friendly multi-language source formatting pipeline. It runs\nlanguage formatters, inserts license headers, and normalizes trailing\nwhitespace. Configured per repo via `.kempt.toml`.\n\nSupported targets:\n\n| Language | Extensions    | Formatter                                                          | Config section |\n|----------|---------------|--------------------------------------------------------------------|----------------|\n| Kotlin   | `.kt`, `.kts` | [ktfmt](https://github.com/facebook/ktfmt)                         | `[ktfmt]`      |\n| Java     | `.java`       | [google-java-format](https://github.com/google/google-java-format) | `[gjf]`        |\n| Rust     | `.rs`         | `cargo fmt`                                                        | `[rustfmt]`    |\n\n## Install\n\n\u003e The binary is named `kempt` everywhere. The crate is published as\n\u003e `kempt-fmt` because the shorter `kempt` name is already taken on\n\u003e crates.io by an unrelated project; that suffix shows up in the install\n\u003e URLs and the homebrew formula filename below.\n\n### Homebrew (macOS, Linux)\n\n```sh\nbrew install ZacSweers/tap/kempt-fmt\n```\n\n### Shell installer\n\n```sh\ncurl --proto '=https' --tlsv1.2 -LsSf https://github.com/ZacSweers/kempt/releases/latest/download/kempt-fmt-installer.sh | sh\n```\n\n### Cargo\n\n```sh\ncargo install kempt-fmt\n```\n\n### Notes\n\nA working `java` (JDK 17+) on `PATH` is required to run ktfmt and gjf (unless using `native`).\nA working `cargo fmt` on `PATH` is required when `[rustfmt]` is enabled.\n\n## Quick start\n\nIn an existing repo:\n\n```sh\nkempt init             # writes .kempt.toml + config/license-header.txt\nkempt install-hook     # writes .git/hooks/pre-commit\nkempt format           # format everything once\n```\n\n`kempt init` scans the repo and tailors the starter config: `[ktfmt]` is\nemitted only when `.kt`/`.kts` files exist, `[gjf]` only when `.java` files\nexist, and `[rustfmt]` only when `.rs` files exist. An empty repo gets all\nformatter sections. The versions written into the starter are the latest\navailable at the time kempt was built; an automated workflow keeps them\ncurrent.\n\n`kempt check` is the read-only variant. It exits non-zero if any file would\nchange. That's what you want in CI formatting checks.\n\n## Configuration\n\n`.kempt.toml` at the repo root. Every section is optional, missing sections\ndisable that step.\n\n```toml\n[ktfmt]\nversion = \"0.62\"\nstyle = \"google\"           # google | kotlinlang | meta\n\n[gjf]\nversion = \"1.35.0\"\nstyle = \"google\"           # google | aosp\nnative = \"auto\"            # auto | always | never\n\n[rustfmt]\n\n[license-header]\nfile = \"config/license-header.txt\"             # supports ${YEAR}\n\n# Optional per-tool overrides. Either field may be omitted; the global\n# [license-header].file is the fallback for `file`.\n[ktfmt.license-header]\nexcludes = \"config/license-excludes-kt.txt\"\n\n[gjf.license-header]\nfile = \"config/license-header-java.txt\"        # overrides global for .java\nexcludes = \"config/license-excludes-java.txt\"\n\n[rustfmt.license-header]\nfile = \"config/license-header-rust.txt\"        # overrides global for .rs\nexcludes = \"config/license-excludes-rs.txt\"\n\n[paths]\n# Universal exclude applied before any tool's own filter. Inline array OR\n# a path to a text file (one glob per line, `#` comments).\nexclude = [\"**/build/**\", \"**/target/**\"]\n\n# Per-tool path scope. Each tool has its own include/exclude with sensible\n# language defaults; you only set these to narrow further.\n[ktfmt.paths]\n# defaults: include = [\"**/*.kt\", \"**/*.kts\"], exclude = []\ninclude = [\"**/src/**/*.kt\", \"**/src/**/*.kts\"]\nexclude = \"config/ktfmt-skip.txt\"   # polymorphic: array or file path\n\n[gjf.paths]\n# defaults: include = [\"**/*.java\"], exclude = []\nexclude = [\"**/*Generated.java\"]\n\n[rustfmt.paths]\n# defaults: include = [\"**/*.rs\"], exclude = []\n\n[whitespace.paths]\n# defaults: include = [\"**/*.kt\", \"**/*.kts\", \"**/*.java\", \"**/*.rs\"], exclude = []\n\n[whitespace]\nstrip-trailing = true   # strip trailing space/tab/CR from every line\nfinal-newline = true    # ensure files end with one trailing newline\n\n[hook]\nmode = \"format\"            # format | check\n```\n\nThe license header file is a literal template. `${YEAR}` is expanded at write-time.\n\n`[license-header]` sets the default template used by every language.\n`[ktfmt.license-header]`, `[gjf.license-header]`, and\n`[rustfmt.license-header]` override per tool: `file` swaps in a different\ntemplate for that tool's languages, and `excludes` points at an exclude-list\nspecific to that tool. Either field is optional.\nIf neither the global section nor the tool override supplies a `file`, no\nheader is inserted for that language.\n\nThe exclude files are plain text, one glob per line, `#` comments allowed.\n\n### Two kinds of excludes\n\nkempt has two exclude mechanisms because they answer different questions:\n\n| Where                                      | Question it answers                          | Example use                                                          |\n|--------------------------------------------|----------------------------------------------|----------------------------------------------------------------------|\n| `[paths].exclude` (inline list)            | \"Should kempt touch this file at all?\"       | Build output, test fixtures, generated code, vendored upstream files |\n| `[\u003ctool\u003e.license-header].excludes` (file pointer) | \"Should kempt insert a header in this file?\" | Files with their own license header that should still be formatted   |\n\nIf a file matches `[paths].exclude`, kempt skips it completely, no formatter and\nno header. If a file is in a license-header excludes file but not in\n`[paths].exclude`, kempt still formats it with its configured tool; it just won't\nprepend a header.\n\nWhen in doubt, prefer `[paths].exclude`. Reach for the license-header excludes\nonly when you genuinely want the formatter to run but the header to stay off\n(rare in practice).\n\n### Per-tool path scope\n\nEach tool has its own `paths.include` / `paths.exclude` with language\ndefaults so you only configure these when you need to narrow further:\n\n| Tool                 | Default include                                   | Default exclude |\n|----------------------|---------------------------------------------------|-----------------|\n| `[ktfmt.paths]`      | `[\"**/*.kt\", \"**/*.kts\"]`                         | `[]`            |\n| `[gjf.paths]`        | `[\"**/*.java\"]`                                   | `[]`            |\n| `[rustfmt.paths]`    | `[\"**/*.rs\"]`                                     | `[]`            |\n| `[whitespace.paths]` | `[\"**/*.kt\", \"**/*.kts\", \"**/*.java\", \"**/*.rs\"]` | `[]`            |\n\nThe global `[paths].exclude` is applied first as a universal filter; each\ntool's own `include` / `exclude` then narrows further. A file is processed\nby a given tool iff:\n\n- It is not matched by `[paths].exclude` (universal exclusion), AND\n- It is matched by that tool's `paths.include`, AND\n- It is not matched by that tool's `paths.exclude`.\n\nLicense-header insertion is determined by file extension (`.kt`/`.kts`\nget the kt header, `.java` gets the java header, `.rs` gets the rust header)\nplus the per-tool `license-header.excludes` list. It is intentionally NOT\ngated on tool path scope so you can configure `[license-header]` without\nconfiguring `[ktfmt]` and still get headers on kt files.\n\n### Polymorphic include / exclude\n\nEvery `include` and `exclude` field accepts either an inline array or a\npath to a text file (one glob per line, `#` comments allowed):\n\n```toml\n[ktfmt.paths]\ninclude = [\"**/*.kt\", \"**/*.kts\"]      # inline\nexclude = \"config/ktfmt-skip.txt\"      # file path\n\n[paths]\nexclude = \"config/global-excludes.txt\" # file path also works for the global\n\n[ktfmt.license-header]\nexcludes = \"config/license-excludes-kt.txt\"  # already file-path-only\n```\n\nFile paths are resolved relative to the repo root. The format is:\n\n```\n# header comment\n**/Generated.kt\n**/build-cache/**\n# another comment\n**/Skip.kt\n```\n\n### File scope\n\n`kempt format` and `kempt check` default to every **tracked** file matching\nthe include globs (via `git ls-files`). Two flags adjust the file set:\n\n| Flag                     | Effect                                                                                                                                                                      |\n|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| (none) or `--all`        | All tracked files. The default \"format everything\" mode. `--all` exists as an explicit alias for symmetry with the other scope flags and so suggestions can be unambiguous. |\n| `--staged`               | Files in the index only. Used by the pre-commit hook.                                                                                                                       |\n| `--discovery=walk`       | Filesystem walk from the repo root. Includes untracked files. Does NOT consult `.gitignore`. `[paths].exclude` is the only filter.                                          |\n| `\u003cfile\u003e...` (positional) | Operate on exactly the listed files. Bypasses scope flags and `[paths].include` / `[paths].exclude`. Useful for targeted runs (e.g. `kempt format src/Foo.kt`).             |\n\n`--all`, `--staged`, `--discovery=walk`, and explicit positional paths are\nall mutually exclusive.\n\nWhen `kempt check` (or `kempt format --dry-run`) finds 30 or fewer files\nneeding formatting, it appends a copy-pasteable command listing those files\nspecifically. Useful for fixing a small subset locally after a CI failure\nwithout touching the rest of the working tree.\n\n`--discovery=walk` is the escape hatch when you have files git doesn't know\nabout (recently dropped in, never staged) and want kempt to format them\nanyway. It deliberately skips `.gitignore` because if you're explicitly\nopting out of VCS-driven discovery, deferring to a VCS-managed ignore file\nis incoherent. Use `[paths].exclude` to filter out build outputs and the\nlike (the defaults already cover `**/build/**` and `**/target/**`). The\n`.git/` directory is always pruned regardless of config.\n\n### Config reference\n\nEvery option, with default. A `-` in the default column means \"no built-in\ndefault; the section that contains it is what enables the feature.\"\n\n| Key                                 | Default                                           | Notes                                                                                                                    |\n|-------------------------------------|---------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------|\n| `[ktfmt].version`                   | -                                                 | Maven Central version. Either a literal `\"0.62\"` or a catalog reference `{ file, key }`. Mutually exclusive with `path`. |\n| `[ktfmt].path`                      | -                                                 | Path to a checked-in jar. Mutually exclusive with `version`.                                                             |\n| `[ktfmt].style`                     | `\"google\"`                                        | `google` / `kotlinlang` / `meta`                                                                                         |\n| `[ktfmt.paths].include`             | `[\"**/*.kt\", \"**/*.kts\"]`                         | Inline array or path to a glob-list file.                                                                                |\n| `[ktfmt.paths].exclude`             | `[]`                                              | Inline array or path to a glob-list file.                                                                                |\n| `[ktfmt.license-header].file`       | inherits `[license-header].file`                  | Per-tool template override.                                                                                              |\n| `[ktfmt.license-header].excludes`   | none                                              | Path to a glob list (one per line, `#` comments).                                                                        |\n| `[gjf].version`                     | -                                                 | GitHub release version. Either a literal or a catalog reference `{ file, key }`. Mutually exclusive with `path`.         |\n| `[gjf].path`                        | -                                                 | Path to a checked-in jar or native binary. Mutually exclusive with `version`.                                            |\n| `[gjf].style`                       | `\"google\"`                                        | `google` / `aosp`                                                                                                        |\n| `[gjf].native`                      | `\"auto\"`                                          | `auto` / `always` / `never`. See \"Native gjf\".                                                                           |\n| `[gjf.paths].include`               | `[\"**/*.java\"]`                                   | Inline array or path to a glob-list file.                                                                                |\n| `[gjf.paths].exclude`               | `[]`                                              | Inline array or path to a glob-list file.                                                                                |\n| `[gjf.license-header].file`         | inherits `[license-header].file`                  | Per-tool template override.                                                                                              |\n| `[gjf.license-header].excludes`     | none                                              | Path to a glob list.                                                                                                     |\n| `[rustfmt.paths].include`           | `[\"**/*.rs\"]`                                     | Inline array or path to a glob-list file.                                                                                |\n| `[rustfmt.paths].exclude`           | `[]`                                              | Inline array or path to a glob-list file.                                                                                |\n| `[rustfmt.license-header].file`     | inherits `[license-header].file`                  | Per-tool template override.                                                                                              |\n| `[rustfmt.license-header].excludes` | none                                              | Path to a glob list.                                                                                                     |\n| `[license-header].file`             | -                                                 | Default license header template, `${YEAR}` expanded at write time. Section absence = no header insertion.                |\n| `[paths].exclude`                   | `[\"**/build/**\", \"**/target/**\"]`                 | Universal exclude, applied before any tool's filter. Inline array or path to a glob-list file.                           |\n| `[whitespace].strip-trailing`       | `true`                                            | Strip trailing space/tab/CR on every line.                                                                               |\n| `[whitespace].final-newline`        | `true`                                            | Ensure files end with exactly one `\\n`.                                                                                  |\n| `[whitespace.paths].include`        | `[\"**/*.kt\", \"**/*.kts\", \"**/*.java\", \"**/*.rs\"]` | Inline array or path to a glob-list file.                                                                                |\n| `[whitespace.paths].exclude`        | `[]`                                              | Inline array or path to a glob-list file.                                                                                |\n| `[hook].mode`                       | `\"format\"`                                        | `format` formats and re-stages. `check` fails the commit if changes are needed.                                          |\n\nSections that are entirely optional: `[ktfmt]`, `[gjf]`, `[rustfmt]`,\n`[license-header]`, `[ktfmt.license-header]`, `[gjf.license-header]`,\n`[rustfmt.license-header]`.\nOmitting a section disables that step. `[paths]`, `[whitespace]`, and\n`[hook]` are always present (with the defaults above).\n\n## Subcommands\n\n| Command              | Behavior                                                                                                                                 |\n|----------------------|------------------------------------------------------------------------------------------------------------------------------------------|\n| `kempt format`       | Format files in place.                                                                                                                   |\n| `kempt check`        | Dry-run; exits non-zero if changes are needed. Suitable for CI.                                                                          |\n| `kempt init`         | Scaffold `.kempt.toml` plus a starter `config/license-header.txt`. Detects `.kt`/`.java`/`.rs` to decide which sections to write.        |\n| `kempt install-hook` | Write a `.git/hooks/pre-commit` that calls `kempt hook`.                                                                                 |\n| `kempt hook`         | Run as the pre-commit hook. Not normally invoked manually.                                                                               |\n| `kempt update`       | Download formatter jars/binaries per config. Pre-warms the cache.                                                                        |\n| `kempt upgrade`      | Bump tool versions in `.kempt.toml` to the latest upstream release. Preserves comments and formatting. `--dry-run` previews.             |\n| `kempt vendor`       | Download and copy formatter binaries into the repo for check-in (default dir `config/bin/`). Prints the `path = \"...\"` snippet to paste. |\n| `kempt cache list`   | Show cached artifacts and their sizes.                                                                                                   |\n| `kempt cache prune`  | Remove cached artifacts not referenced by `.kempt.toml`.                                                                                 |\n\n### Flags\n\n`kempt format` and `kempt check` share the same flag set:\n\n| Flag                        | Default                        | Effect                                                                        |\n|-----------------------------|--------------------------------|-------------------------------------------------------------------------------|\n| `--all`                     | (the implicit default)         | All tracked files. Explicit alias of the default for unambiguous suggestions. |\n| `--staged`                  | off                            | Only files in the git index.                                                  |\n| `--discovery=\u003cvcs\\|walk\u003e`   | `vcs`                          | `walk` walks the filesystem; doesn't consult `.gitignore`.                    |\n| `--dry-run` (`format` only) | off                            | Preview without writing. Equivalent to `kempt check`.                         |\n| `\u003cfile\u003e...` (positional)    | -                              | Process exactly the listed files. Bypasses scope flags and `[paths]` filters. |\n| `--config \u003cPATH\u003e`           | `.kempt.toml` in the repo root | Override the config file path.                                                |\n\n`--all`, `--staged`, `--discovery=walk`, and explicit positional paths are\nmutually exclusive.\n\n`kempt install-hook` takes `--force` to overwrite an existing pre-commit\nhook (default refuses).\n\n`kempt vendor` takes `--dir \u003cPATH\u003e` (default `config/bin`) for the target\ndirectory.\n\n`kempt cache prune` takes `--all` to wipe every cached artifact regardless\nof config.\n\n### Failure messages\n\nWhen `kempt check` finds something wrong it prints the offending file paths\nfollowed by an actionable trailer that's tailored to scope and content:\n\n- Default scope, files need formatting:\n  `kempt: 3 files need formatting. Run `kempt format --all` to apply.`\n  When the count is `\u003c= 30`, a copy-pasteable per-file command is appended.\n- `--staged`: trailer suggests `kempt format --staged`.\n- `--discovery=walk`: trailer suggests `kempt format --discovery=walk`.\n- Hook in `[hook] mode = \"check\"`: trailer reminds about\n  `git commit --no-verify` as the bypass.\n- Mixed format diffs + parse errors: trailer says \"fix the syntax errors\n  above, then run `kempt format` for the rest.\"\n- Pure parse errors: trailer says formatting can't proceed until they're\n  fixed.\n\nktfmt/gjf parse errors are surfaced with their file:line:col message; JVM\ndeprecation warnings are filtered out so the actual error is what you read.\n\n## Pre-commit hook\n\n`kempt install-hook` writes a one-line `pre-commit` that calls `kempt hook`.\nThe hook does, in order:\n\n1. Collect staged files matching the config's path globs.\n2. Refuse to run if any of those files have unstaged modifications. This is\n   the \"partial staging\" case where formatting and re-staging would silently pull\n   unstaged hunks into the commit. kempt doesn't pretend that this is a\n   desirable situation, instead refusing to proceed and asking you to pick one\n   of three escape hatches:\n   1. Stage the rest\n   2. `git stash --keep-index`\n   3. Commit with `--no-verify`.\n3. Run the full pipeline (license headers, whitespace, ktfmt, gjf, cargo fmt).\n4. `git add` the formatted files back to the index.\n\nExperimental: set `KEMPT_EXPERIMENTAL_PARTIAL_GJF=1` to allow partially\nstaged GJF-managed Java files. kempt formats the staged Java hunks through GJF\nand updates the index directly, without staging unrelated worktree hunks. Other\npartially staged files still use the refusal path above.\n\nSet `[hook] mode = \"check\"` to make the hook fail on any required change\ninstead of formatting in place.\n\n## CI\n\nThe cache lives at `~/.kempt/cache/` by default. Point it elsewhere with\n`KEMPT_CACHE_DIR`. The cache contains versioned jars only, so the cache key\nis just the contents of `.kempt.toml`.\n\n### GitHub Actions\n\n```yaml\nname: format-check\non: [push, pull_request]\n\njobs:\n  check:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/setup-java@v5\n        with:\n          distribution: zulu\n          java-version: '21'\n      - uses: actions/cache@v5\n        with:\n          path: ~/.kempt/cache\n          key: kempt-${{ hashFiles('.kempt.toml') }}\n      - name: Install kempt\n        run: |\n          curl --proto '=https' --tlsv1.2 -LsSf \\\n            https://github.com/ZacSweers/kempt/releases/latest/download/kempt-fmt-installer.sh | sh\n      - name: Pre-warm cache\n        run: kempt update\n      - name: Check formatting\n        run: kempt check\n```\n\nWhat to cache:\n\n- `~/.kempt/cache/` (or wherever `KEMPT_CACHE_DIR` points). Jars are 5 to 30\n  MiB each.\n\nWhat not to cache:\n\n- The Rust toolchain. kempt is shipped as a static binary; no `cargo`\n  required at run time.\n\n`kempt update` is optional; the formatter will download on demand. Splitting\nit out makes the failure mode (network down vs. real format error) easier to\nread in CI logs.\n\n## Versioning the formatter binaries\n\nPin `version = \"...\"` in each tool's section. The cache is version-suffixed,\nso multiple repos with different pins coexist without re-downloading. To\nupdate, change the version in `.kempt.toml`, run `kempt update`, then\n`kempt cache prune` to drop the old jar.\n\n### `kempt upgrade`\n\nBump the literal `version = \"...\"` entries in `.kempt.toml` to the latest\nupstream release in one shot:\n\n```sh\nkempt upgrade            # apply\nkempt upgrade --dry-run  # preview\n```\n\nComments and formatting in `.kempt.toml` are preserved (the rewrite goes\nthrough `toml_edit`). Sections that use `path = \"...\"` (vendored binaries)\nor a catalog reference are skipped with a note explaining where to make\nthe change. After a successful upgrade, run `kempt update` to download the\nnew artifacts into the cache.\n\nThis is the offline-style equivalent of running Renovate locally.\n\n### Catalog references (Gradle `libs.versions.toml`)\n\nIf you already track formatter versions in a Gradle version catalog,\n`version` accepts a reference table instead of a literal:\n\n```toml\n[ktfmt]\nversion = { file = \"gradle/libs.versions.toml\", key = \"ktfmt\" }\n\n[gjf]\nversion = { file = \"gradle/libs.versions.toml\" }   # key defaults to \"gjf\"\n```\n\nkempt resolves the reference by reading the catalog's `[versions]` table\nat startup. The `key` field defaults to the tool name (`ktfmt` or `gjf`)\nso the common case is even shorter:\n\n```toml\n[ktfmt]\nversion = { file = \"gradle/libs.versions.toml\" }\n```\n\nResolution rules:\n- Path is relative to the repo root (or absolute).\n- Only literal version strings are accepted; structured \"rich\" Gradle\n  versions (`{ strictly = \"1.0\", require = \"...\" }`) error with a clear\n  message.\n- The catalog file is parsed at most once per kempt invocation, even if\n  both `[ktfmt]` and `[gjf]` reference the same file.\n\nThis works well with **Dependabot**, which doesn't natively understand\n`.kempt.toml`: keep a dummy `[libraries]` entry in `libs.versions.toml`\nthat Dependabot can spot, and Renovate-style customization isn't needed.\n\n### Auto-updating with Renovate\n\nRenovate has no built-in knowledge of `.kempt.toml`, but its custom regex\nmanager covers it cleanly. Add this to your `renovate.json`:\n\n```json\n{\n  \"customManagers\": [\n    {\n      \"customType\": \"regex\",\n      \"fileMatch\": [\"(^|/)\\\\.kempt\\\\.toml$\"],\n      \"matchStrings\": [\n        \"\\\\[ktfmt\\\\][^\\\\[]*?version\\\\s*=\\\\s*\\\"(?\u003ccurrentValue\u003e[^\\\"]+)\\\"\"\n      ],\n      \"datasourceTemplate\": \"maven\",\n      \"registryUrlTemplate\": \"https://repo1.maven.org/maven2\",\n      \"depNameTemplate\": \"com.facebook:ktfmt\"\n    },\n    {\n      \"customType\": \"regex\",\n      \"fileMatch\": [\"(^|/)\\\\.kempt\\\\.toml$\"],\n      \"matchStrings\": [\n        \"\\\\[gjf\\\\][^\\\\[]*?version\\\\s*=\\\\s*\\\"(?\u003ccurrentValue\u003e[^\\\"]+)\\\"\"\n      ],\n      \"datasourceTemplate\": \"maven\",\n      \"registryUrlTemplate\": \"https://repo1.maven.org/maven2\",\n      \"depNameTemplate\": \"com.google.googlejavaformat:google-java-format\"\n    }\n  ]\n}\n```\n\nEach manager scans `.kempt.toml`, finds the first `version = \"...\"` after a\nmatching tool section header (`[ktfmt]` or `[gjf]`), and tracks the\ncorresponding Maven Central coordinate. Renovate opens a PR per upstream\nrelease.\n\n`[^\\[]*?` in the match string keeps the lookahead within the current\nsection, so a later section's `version` value isn't matched by the wrong\nmanager. Tool sections that use `path = \"...\"` instead of `version` are\nskipped automatically (no `version` line to match).\n\n### Dependabot\n\nDependabot does not support arbitrary regex-based managers, so there is no\ndirect equivalent for `.kempt.toml`. If Dependabot is a hard requirement,\nthe workaround is to keep the version pin in a file Dependabot already\nunderstands (e.g. a Gradle `libs.versions.toml`) and copy it into\n`.kempt.toml` manually or via a small CI step. Native catalog support in\nkempt itself is on the table for a future release.\n\n### Native gjf\n\nStarting with gjf 1.20.0, Google publishes GraalVM-native binaries alongside\nthe JVM jar. They start in roughly 20ms instead of ~500ms (JVM warmup),\ndon't need a JDK, and skip the `--add-opens` dance entirely. Native builds\nexist for `darwin-arm64`, `linux-x86-64`, `linux-arm64` (1.26.0+), and\n`windows-x86-64`. There is no Intel macOS (`darwin-x86-64`) native build,\nand no native ktfmt at all.\n\n`[gjf].native` controls which artifact kempt downloads:\n\n- `auto` (default): native when published for this platform + version, jar\n  otherwise.\n- `always`: native always; errors if not published for the host.\n- `never`: jar always.\n\nktfmt is unaffected (always JVM).\n\n### Checking in the binaries (hermetic / offline builds)\n\nIf you want full reproducibility or your CI can't reach Maven Central and\nGitHub releases, commit the formatter binaries and point at them with\n`path` instead of `version`. The fast path:\n\n```sh\nkempt vendor                    # downloads (if needed) and copies into config/bin/\nkempt vendor --dir tools/bin    # custom directory\n```\n\n`vendor` skips any tool already using `path = ...` and prints the snippet\nto paste into `.kempt.toml`:\n\n```toml\n[ktfmt]\npath = \"config/bin/ktfmt-0.62.jar\"\n\n[gjf]\npath = \"config/bin/gjf-1.35.0.jar\"\n```\n\n`path` and `version` are mutually exclusive; exactly one must be set per\ntool. Relative paths resolve against the repo root, absolute paths are used\nas-is. When `path` is set, kempt never touches the cache for that tool:\n\n- `kempt update` skips it.\n- `kempt cache prune` ignores it.\n- `kempt format` and the hook run the in-repo binary directly.\n\nIf the file is missing at the resolved path, kempt errors out with the full\npath so you can spot the typo quickly.\n\n## License\n\nApache 2.0. See [LICENSE](LICENSE.txt).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzacsweers%2Fkempt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzacsweers%2Fkempt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzacsweers%2Fkempt/lists"}