A pre-commit hook for managing symlinks to AI configuration files stored in a hidden directory
Project description
ai-deroot-pre-commit
A pre-commit hook for managing symlinks to AI configuration files stored in a hidden directory.
Overview
Many AI tools (Claude Code, Gemini, etc.) require configuration files like CLAUDE.md, GEMINI.md, and project-specific files like specs/, templates/, and memory/ to be present at the repository root. However, committing these files directly to your main branch can clutter your repository and create merge conflicts when working across branches.
This pre-commit hook solves this by:
- Storing master files in a hidden directory (e.g.,
.llm/) - Creating symlinks from the repository root to the hidden directory
- Managing
.gitignoreto prevent accidental commits of symlinked files - Validating symlinks to ensure they're always properly configured
- Optional: Migrating existing files at the repo root into your hidden directory (copy or move) before creating the symlinks
Usage
Add this to your .pre-commit-config.yaml:
repos:
- repo: https://github.com/shyndman/ai-deroot-pre-commit
rev: v0.0.9 # Use the ref you want to point at
hooks:
- id: setup-managed-symlinks
- id: check-managed-symlinks
- id: update-gitignore-for-symlinks
Full Configuration Example
repos:
- repo: https://github.com/shyndman/ai-deroot-pre-commit
rev: v0.0.9
hooks:
# Sets up symlinks after checkout/merge
- id: setup-managed-symlinks
args:
- --hidden-dir=.llm
- --files=CLAUDE.md,GEMINI.md,specs,templates,memory
- --migrate=move # or copy
# Validates symlinks during normal commits
- id: check-managed-symlinks
args:
- --hidden-dir=.llm
- --files=CLAUDE.md,GEMINI.md,specs,templates,memory
# Ensures .gitignore includes symlinked files
- id: update-gitignore-for-symlinks
args:
- --hidden-dir=.llm
- --files=CLAUDE.md,GEMINI.md,specs,templates,memory
Hook Details
setup-managed-symlinks
When it runs: post-checkout, post-merge
Creates symlinks from the repository root to files in your hidden directory. This ensures that after switching branches or pulling changes, your LLM configuration files are always available where tools expect them.
Features:
- Creates missing symlinks automatically
- Fixes incorrectly pointing symlinks
- Creates the hidden directory if it doesn't exist (supports any dot-dir like
.ai,.llm, etc.) - Optional migration: copy or move existing root files into the hidden dir before creating symlinks
check-managed-symlinks
When it runs: pre-commit (when hidden directory or .gitignore changes)
Validates that all expected symlinks exist and point to the correct locations. Prevents commits when symlinks are broken or missing.
Features:
- Validates symlink targets
- Detects broken symlinks
- Ensures source files exist
- Provides clear error messages with fix instructions
update-gitignore-for-symlinks
When it runs: pre-commit
Automatically adds symlinked files to .gitignore to prevent accidental commits. This hook will modify .gitignore and require you to stage the changes.
Features:
- Adds missing entries to
.gitignore - Preserves existing
.gitignorecontent - Adds descriptive comments
- Only runs when changes are needed
Configuration Options
--hidden-dir
Specifies the directory where master files are stored.
- Default: None (required parameter)
- Example:
--hidden-dir=.llm - Note: Can be any directory name, doesn't need to start with
.
--files
Comma-separated list of files/directories to manage as symlinks.
- Default: None (required parameter)
- Example:
--files=CLAUDE.md,GEMINI.md,specs,templates,memory - Note: Supports both files and directories
Workflow Example
-
Initial setup:
# Create your hidden directory and files mkdir .llm echo "# Claude Configuration" > .llm/CLAUDE.md mkdir .llm/specs # Install pre-commit hooks pre-commit install-hooks # Create initial symlinks pre-commit run --hook-stage post-checkout setup-managed-symlinks
-
After setup, symlinks are created automatically:
git checkout main # setup-managed-symlinks runs automatically # Creates: CLAUDE.md -> .llm/CLAUDE.md # specs -> .llm/specs
-
The
.gitignoreis updated automatically:git add file.py git commit -m "Add feature" # update-gitignore-for-symlinks runs # Adds entries to .gitignore if needed
Important Notes
File Conflicts
If you have existing files at the target locations, the setup-managed-symlinks hook will warn you and not overwrite them. You'll need to manually resolve these conflicts by either:
- Moving the existing file to the hidden directory
- Removing the existing file if it's no longer needed
- Updating your
.gitignoreif the file should be tracked
Hook Stages
The hooks are designed to run at specific stages:
setup-managed-symlinks:post-checkout,post-merge- Ensures symlinks exist after branch operationscheck-managed-symlinks:pre-commit- Validates before commits (only when relevant files change)update-gitignore-for-symlinks:pre-commit- Updates.gitignorebefore commits
Updating File Lists
When you add or remove files from the --files argument, you must also update the files regex in the check-managed-symlinks hook configuration to ensure it runs at the appropriate times.
Troubleshooting
Broken Symlinks
If you see errors about broken symlinks:
# Run the setup hook manually
pre-commit run --hook-stage post-checkout setup-managed-symlinks
# Or use pre-commit's built-in command
pre-commit install --hook-type post-checkout
Permission Issues
If you encounter permission issues with symlinks, ensure your filesystem supports symbolic links and that you have appropriate permissions in the repository directory.
Git Ignore Not Updating
If .gitignore entries aren't being added automatically:
# Run the gitignore hook manually
pre-commit run update-gitignore-for-symlinks
git add .gitignore
git commit -m "Update .gitignore for managed symlinks"
Packaging
This project uses the uv_build PEP 517 build backend (pure Python). The package is kept at the repository root (no src/ layout) via:
[build-system]
requires = ["uv_build>=0.9.3,<0.10.0"]
build-backend = "uv_build"
[tool.uv.build-backend]
module-root = ""
module-name = "ai_deroot_pre_commit"
pre-commit installs this hook using its Python language backend by building and installing the package in an isolated environment.
License
Licensed under the Apache License, Version 2.0 (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0).
--migrate
Optionally migrate existing root files into the hidden directory before creating symlinks.
- Values:
copyormove moverelocates the file/dir into the hidden directorycopyduplicates into the hidden directory; the original is replaced by a symlink
Notes:
- Migration only occurs when the source does not yet exist in the hidden directory and the target exists as a real file/dir.
- If both source and target exist, migration is skipped with a warning.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ai_deroot_pre_commit-0.0.9.tar.gz.
File metadata
- Download URL: ai_deroot_pre_commit-0.0.9.tar.gz
- Upload date:
- Size: 6.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
26556425a0ef2c1c58249eb1700bba7f4e5171851185c5fb4fdfbed3b0708760
|
|
| MD5 |
971a01f8888d5a04c2e5ffef6cdb5b65
|
|
| BLAKE2b-256 |
79088242e69e4788ca47e4aca9539b90d9a845084186a8433bce694361246f06
|
File details
Details for the file ai_deroot_pre_commit-0.0.9-py3-none-any.whl.
File metadata
- Download URL: ai_deroot_pre_commit-0.0.9-py3-none-any.whl
- Upload date:
- Size: 7.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3c619685989835d132e6f35e75babf902ea5d20ff0eeecf96555f0f82875a9b7
|
|
| MD5 |
b86253600396f8d147eafe38b278de99
|
|
| BLAKE2b-256 |
33824dfa3e973c9ff0d59db3c368b22a3152e3528a1a27983a90fe7265bab6ee
|