@ottoai/uignore
v1.2.0
Published
Unified ignore rules for AI coding tools — enforce .uignore exclusions via hooks for Claude Code and Gemini CLI
Maintainers
Readme
Ignoring files
This document provides an overview of the uignore (.uignore) feature for AI coding tools.
uignore gives you a single .uignore file that works across all supported AI coding tools,
similar to .gitignore (used by Git). Adding paths to your .uignore file will exclude them
from all supported AI tools, although they will still be visible to other services (such as Git).
Supported tools
| Tool | Hook | Config file |
|---|---|---|
| Claude Code | PreToolUse | .claude/settings.json |
| Gemini CLI | BeforeTool | .gemini/settings.json |
| Cursor | beforeReadFile | .cursor/hooks.json |
| Windsurf | pre_read_code | .windsurf/hooks.json |
| Codex CLI | — | coming soon |
How it works
When you add a path to your .uignore file, all supported AI tools will exclude matching files
and directories from their operations. For example, when Claude Code tries to read a file, any
paths in your .uignore will be automatically blocked before the tool call executes.
uignore registers a native hook with each AI tool. Before every file operation, the hook checks
whether the target path matches any rule in your .uignore. If it does, the hook exits with
code 2 — a hard block — and the tool is prevented from accessing the file.
uignore also supports hierarchical .uignore files. Starting from the target file's directory,
it searches upward through parent directories to find all .uignore files up to the project
root. Patterns from all discovered files are combined, with files closer to the root applied
first.
A global ~/.uignore file is also supported and loaded before project-level files, giving
you personal rules that apply across every project.
Since hooks are registered in config files that can be committed to git, the whole team gets the same rules automatically.
Installation
npm install -g @ottoai/uignoreQuick start (no global install)
You can use uignore without a global install via npx:
npx @ottoai/uignore init
npx @ottoai/uignore install --yesNote: because npx resolves a new path each time, the hook command written
into your settings files will point to the npx cache. Run uignore install
once with a global install (npm install -g @ottoai/uignore) for a stable
path, or use uignore update after each npm update -g.
How to use .uignore
To enable .uignore:
- Run
uignore initin your project root to create a.uignorefile. - Run
uignore installto register the hook with all supported AI tools. - Commit both files to git so your whole team benefits.
uignore init # creates a .uignore template
uignore install # registers hooks for all supported toolsgit add .uignore .claude/settings.json .gemini/settings.json .cursor/hooks.json .windsurf/hooks.json
git commit -m "chore: add uignore"To add a file or directory to .uignore:
- Open your
.uignorefile. - Add the path or file you want to ignore, for example:
secrets/orapikeys.txt.
You can update your .uignore file at any time. Changes take effect immediately — no restart
required.
To remove a path from .uignore, delete the relevant line.
.uignore syntax
For the most part, .uignore follows the conventions of .gitignore files:
- Blank lines and lines starting with
#are ignored. - Standard glob patterns are supported (such as
*,?, and[]). - Putting a
/at the end will only match directories. - Putting a
/at the beginning anchors the path relative to the.uignorefile. !negates a pattern.
.uignore examples
You can use .uignore to ignore directories and files:
# Exclude your secrets/ directory and all subdirectories
secrets/
# Exclude your apikeys.txt file
apikeys.txtYou can use wildcards in your .uignore file with *:
# Exclude all .pem files
*.pem
# Exclude all .env files
.env
.env.*You can exclude files and directories from exclusion with !:
# Exclude all .md files except README.md
*.md
!README.mdYou can use hierarchical .uignore files for subtree-specific rules:
project/
.uignore # global rules — apply everywhere
src/
.uignore # src-specific rules
secrets/
.uignore # fine-grained rules for this subtree onlyCLI reference
uignore init [dir] [-i] [--force]
Creates a .uignore file. Without flags, copies a sensible default template.
uignore init # create .uignore from template
uignore init --interactive # guided category selection
uignore init ./myapp # create in ./myapp
uignore init --force # overwrite an existing .uignoreWith --interactive (-i), you are prompted to include/exclude each category:
secrets, dependencies, build output, lock files, editor noise, and test coverage.
Use --force (or -f) to overwrite an existing .uignore without prompting.
uignore add <pattern> [--global]
Appends a pattern to your .uignore (or ~/.uignore with --global). Skips duplicates.
uignore add secrets/ # add to project .uignore
uignore add *.pem --global # add to ~/.uignoreuignore remove <pattern> [--global]
Removes a pattern from your .uignore (or ~/.uignore with --global). Exits with code 1
if the pattern is not found.
uignore remove secrets/ # remove from project .uignore
uignore remove *.pem --global # remove from ~/.uignoreuignore check <file> [dir]
Tests whether a file would be blocked by the active .uignore rules, including ~/.uignore.
uignore check secrets/apikey.txt
# ✗ Blocked: "secrets/apikey.txt" matches a rule in .uignore
uignore check src/index.ts
# ✓ Allowed: "src/index.ts"uignore list [dir]
Lists all .uignore files found in the project tree and their active rules, including
~/.uignore if it exists.
uignore list~/.uignore (2 rules)
*.pem
.env
.uignore (3 rules)
secrets/
node_modules/
dist/
src/.uignore (1 rule)
internal/uignore validate [dir]
Validates all .uignore files for syntax errors and warns on patterns that do not match
any git-tracked file (possible typo or obsolete rule). Exits with code 1 if issues found.
uignore validate
# ✓ .uignore: 5 rules, all valid
# ⚠ .uignore:3: pattern "scret/" does not match any tracked fileuignore diff [dir] [--all] [--json]
Shows which git-tracked files are blocked by the active .uignore rules. Useful for
auditing rules before rolling out to a team.
uignore diff # show only blocked files
uignore diff --all # show blocked and allowed files
uignore diff --json # machine-readable JSON output (for CI pipelines)
uignore diff --all --json # JSON with both blocked and allowed arraysWith --json, the output format is:
{
"total": 42,
"blocked": ["secrets/key.pem"],
"allowed": ["src/index.ts"]
}The "allowed" key is only included when --all is also passed.
uignore doctor [dir]
Diagnoses common configuration problems:
- Hook script exists at its compiled path
.uignorefile exists and has valid syntax- Each tool's settings file exists and is valid JSON
- uignore hook is registered for Claude Code and Gemini CLI
- Hook path in settings still points to an existing file
uignore doctor
# ✓ Hook script found: /usr/local/lib/node_modules/@ottoai/uignore/dist/hook.js
# ✓ .uignore found and syntax valid
# ✓ Claude Code: hook registered and path valid
# ✗ Gemini CLI: hook path no longer exists — run `uignore update` to fixExits with code 1 if any issues are found.
uignore install [dir] [--claude] [--gemini] [--cursor] [--windsurf] [--codex] [--all] [-y] [--dry-run]
Registers the uignore hook with the specified AI tool(s). Defaults to --all.
- If the tool's settings file does not exist, it is created automatically.
- If it already exists, you are prompted to choose:
[1] auto— merge automatically[2] manual— show a snippet to merge yourself
uignore install # install for all supported tools
uignore install --claude # Claude Code only
uignore install --gemini # Gemini CLI only
uignore install --cursor # Cursor only
uignore install --windsurf # Windsurf only
uignore install --codex # Codex CLI (coming soon)
uignore install --yes # auto-merge without prompting (CI-friendly)
uignore install --dry-run # preview what would be writtenuignore update [dir] [--claude] [--gemini] [--cursor] [--windsurf] [--all]
Re-writes the hook command in each tool's settings file with the current absolute path.
Run this after upgrading @ottoai/uignore to fix stale hook paths.
uignore update # update all tools
uignore update --claude # Claude Code onlyuignore uninstall [dir] [--claude] [--gemini] [--cursor] [--windsurf] [--codex] [--all]
Removes the uignore hook from the specified AI tool(s). Defaults to --all.
uignore uninstall # remove from all supported tools
uignore uninstall --claude # Claude Code only
uignore uninstall --gemini # Gemini CLI only
uignore uninstall --cursor # Cursor only
uignore uninstall --windsurf # Windsurf onlyuignore status [dir] [--claude] [--gemini] [--cursor] [--windsurf] [--all]
Shows whether the hook is installed for each tool and lists the active rules from .uignore.
uignore status~/.uignore : found (/Users/you/.uignore)
.uignore : found (/your/project/.uignore)
Claude Code
Hook installed : yes
Settings file : /your/project/.claude/settings.json
Gemini CLI
Hook installed : yes
Settings file : /your/project/.gemini/settings.json
Cursor
Hook installed : yes
Settings file : /your/project/.cursor/hooks.json
Windsurf
Hook installed : yes
Settings file : /your/project/.windsurf/hooks.json
Active rules (5):
.env
.env.*
*.pem
secrets/
node_modules/uignore install-git-hook [dir]
Installs a git pre-commit warning hook. Before each commit, it checks whether any staged
files match .uignore rules and prints a warning — without blocking the commit.
uignore install-git-hook # install in current repo
uignore uninstall-git-hook # remove ituignore completions <bash|zsh|fish>
Prints a shell completion script. Add to your shell config to get tab completion for all subcommands and flags.
# bash — add to ~/.bashrc
eval "$(uignore completions bash)"
# zsh — add to ~/.zshrc
eval "$(uignore completions zsh)"
# fish — add to ~/.config/fish/config.fish
uignore completions fish | sourceuignore help [command]
Shows help for a specific command, or lists all available commands.
uignore help # list all commands
uignore help init # show detailed help for uignore init
uignore help diff # show detailed help for uignore diffuignore version
Prints the installed version.
uignore version
# @ottoai/uignore v1.1.0dir defaults to the current directory.
Manual hook configuration
If you prefer to configure the hook yourself, add the following to each tool's settings file.
Claude Code — .claude/settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read|Edit|Write|Glob|Grep|NotebookEdit",
"hooks": [
{
"type": "command",
"command": "node /path/to/uignore/dist/hook.js"
}
]
}
]
}
}Gemini CLI — .gemini/settings.json
{
"hooks": {
"BeforeTool": [
{
"matcher": "read_file|write_file|replace|read_many_files|grep_search|glob|list_directory",
"hooks": [
{
"type": "command",
"command": "node /path/to/uignore/dist/hook.js",
"name": "uignore",
"description": "Blocks file access matching .uignore rules"
}
]
}
]
}
}Cursor — .cursor/hooks.json
{
"version": 1,
"hooks": {
"beforeReadFile": [{ "command": "node /path/to/uignore/dist/hook.js" }],
"beforeTabFileRead": [{ "command": "node /path/to/uignore/dist/hook.js" }],
"afterFileEdit": [{ "command": "node /path/to/uignore/dist/hook.js" }],
"afterTabFileEdit": [{ "command": "node /path/to/uignore/dist/hook.js" }]
}
}Windsurf — .windsurf/hooks.json
{
"hooks": {
"pre_read_code": [{ "command": "node /path/to/uignore/dist/hook.js" }],
"pre_write_code": [{ "command": "node /path/to/uignore/dist/hook.js" }]
}
}uignore install writes all files automatically with the correct absolute path.
Development
npm install # install dependencies
npm run build # compile TypeScript → dist/
npm test # run all testsThe source is TypeScript. Tests use Node's built-in test runner with tsx for on-the-fly
compilation — no separate compile step needed to run tests.
Project structure
src/
hook.ts # PreToolUse / BeforeTool / beforeReadFile / pre_read_code hook
pre-commit.ts # git pre-commit warning script
cli/
index.ts # entry point — USAGE + command dispatch
shared.ts # shared helpers (parseFlags, loadSettings, …)
hook-script.ts # HOOK_SCRIPT path constant
completions.ts # bash / zsh / fish completion strings
commands/
add.ts # uignore add
remove.ts # uignore remove
help.ts # uignore help
check.ts # uignore check
completions.ts # uignore completions
diff.ts # uignore diff
doctor.ts # uignore doctor
git-hook.ts # uignore install-git-hook / uninstall-git-hook
init.ts # uignore init
install.ts # uignore install
list.ts # uignore list
status.ts # uignore status
uninstall.ts # uignore uninstall
update.ts # uignore update
validate.ts # uignore validate
version.ts # uignore version
tools/
types.ts # shared interfaces
claude.ts # Claude Code hook config
gemini.ts # Gemini CLI hook config
cursor.ts # Cursor install / uninstall / status / update
windsurf.ts # Windsurf install / uninstall / status / update
codex.ts # Codex coming-soon stubs
templates/
.uignore # default template created by `uignore init`
__tests__/
hook.test.ts # integration tests
dist/ # compiled output (tsc)