A lightweight file normalization CLI tool for AI coding agents.
Standardizes file formatting as a finishing step after code editing.
cargo install fininix run github:tsukasaI/fini -- .
nix profile install github:tsukasaI/finibrew tap tsukasaI/fini https://github.com/tsukasaI/fini
brew install tsukasaI/fini/finiDownload from GitHub Releases.
Add to your .pre-commit-config.yaml:
repos:
- repo: https://github.com/tsukasaI/fini
rev: v0.3.0 # Use the latest release tag
hooks:
- id: fini # Auto-fix mode
# or
- id: fini-check # Check-only mode (for CI)Note: Requires fini to be installed (cargo install fini or via Homebrew/Nix).
fini . # Fix current directory
fini src/main.rs # Fix specific file
fini --check . # Check only, exit 1 if problems
fini --diff . # Preview changes
fini --quiet . # Output only filenames
fini -v . # Verbose: show all processed files
fini --init # Generate fini.toml template
cat file.txt | fini --stdin # Read from stdin, output to stdout-v, --verbose Show all processed files (including clean ones)
--stdin Read from stdin, output to stdout
--color Force colored output
--no-color Disable colored output
--no-progress Hide progress bar
--max-blank-lines <N> Limit consecutive blank lines to N
--keep-zero-width Keep zero-width characters (default: remove)
--keep-leading-blanks Keep leading blank lines (default: remove)
--fix-code-blocks Remove code block remnants (```lang markers)
--exclude <PATTERN> Exclude files matching glob pattern (repeatable)
--init Generate fini.toml configuration template
--config <PATH> Use specific config file
Create fini.toml in your project root (or run fini --init):
# Exclude files matching these patterns (gitignore-style globs)
# exclude = ["vendor/", "node_modules/", "*.min.js"]
[normalize]
max_blank_lines = 2 # Limit consecutive blank lines
remove_zero_width = true # Remove zero-width characters
remove_leading_blanks = true
fix_code_blocks = false # Remove ``` markersCLI arguments > fini.toml > defaults
Searches upward from current directory, stops at git root.
fini reads .editorconfig and warns if settings conflict with its fixed behaviors (always trims whitespace, always LF, always adds final newline).
| Rule | Description | Default |
|---|---|---|
| EOF newline | Add \n if missing, normalize multiple trailing newlines |
On |
| Line endings | CRLF/CR to LF | On |
| Trailing whitespace | Remove trailing spaces and tabs | On |
| Full-width spaces | Fix U+3000 to regular space (with warning) | On |
| Leading blank lines | Remove blank lines at file start | On |
| Zero-width characters | Remove ZWSP, ZWJ, ZWNJ, etc. (preserve BOM at start) | On |
| Consecutive blank lines | Limit to N blank lines (--max-blank-lines) |
Off |
| Code block remnants | Remove ``` markers (--fix-code-blocks) |
Off |
Suppress detections per-line with fini:ignore directives. Works with any comment syntax.
# TODO: intentional reminder fini:ignore
print("debug") # fini:ignore debug
# fini:ignore-next-line
API_KEY = "sk_test_example"| Directive | Effect |
|---|---|
fini:ignore |
Suppress all detections on this line |
fini:ignore todo,debug |
Suppress only listed kinds |
fini:ignore-next-line |
Suppress all detections on the next line |
fini:ignore-next-line secret |
Suppress only listed kinds on the next line |
Kind identifiers: todo, fixme, debug, secret, line-length, fullwidth, zero-width, leading-blanks, blank-lines, code-block
Suppressions are counted in the run summary. Suppressed secret detections are
always reported on stderr with file:line (even with --quiet) so they remain
auditable.
Secret detections are reported hint-only — the matched value is never printed.
--diff output masks lines matching a secret pattern
([line masked: potential …]), but a secret that no pattern matches will appear
verbatim in a diff: be careful publishing CI logs that include --diff output.
Disabling secret detection via fini.toml (detect_secrets = false) prints a
warning to stderr that --quiet does not suppress.
- Binary files (null bytes in first 8KB)
- UTF-16 and other non-UTF-8 text files (unsupported encodings)
- Symlinks (never followed or rewritten)
- Empty files
- Hidden files (
.foo) .git/directory.gitignorepatterns
Skipped binary / non-UTF-8 / symlink files are counted in the run summary;
use --verbose to list them individually with the skip reason.
Add to .claude/settings.json:
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write|NotebookEdit",
"hooks": [{
"type": "command",
"command": "fini \"$TOOL_INPUT.file_path\""
}]
}]
}
}- uses: tsukasaI/fini@v0.4.0Pin to a release tag as above, or to a full commit SHA for supply-chain hardening.
The action runs fini --check by default. Note that fix mode (without --check)
never fails on detection-only problems (TODOs, debug code, secrets) — always use
--check when gating CI.
- uses: tsukasaI/fini@v0.4.0
with:
files: 'src/ tests/' # Files/directories to check (default: .)
check: 'true' # Check mode, fail if issues found (default: true)
version: 'v0.3.0' # Specific version (default: latest)
verify-attestation: 'false' # Verify SLSA build provenance via `gh attestation verify`
# (default: false). Set to 'true' once your pinned fini
# version has attestations (releases after v0.3.0).See editors/vscode for a VS Code extension that runs fini on save.
| Code | Meaning |
|---|---|
| 0 | Success (no problems / fix completed) |
| 1 | Problems found (--check) |
| 2 | Runtime error (I/O failure, invalid config or exclude pattern) |
Fix mode (without --check) exits 0 even when detection-only problems
(TODOs, debug code, secrets) are reported — they are informational there.
Use fini --check as the CI gate.
This project uses prek for pre-commit hooks.
# Install prek
cargo install prek
# Install git hooks
prek install
# Run hooks manually
prek run --all-filesMIT