All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Profiles are a new way to compose overlays together with AI harness capabilities — instruction files and plugins — into a single named, loadable unit. Where an overlay describes files to place in a repo, a profile describes intent: everything an agent needs for a given workflow. Apply a profile to an ephemeral GitHub Copilot or Claude Code session with repoverlay copilot --profile <name> or repoverlay claude --profile <name>, and the harness places each capability in the right location. Or apply one persistently with repoverlay profile apply <name> --harness <claude|copilot> and manage it with profile list/show/status/remove.
See the guide for details: https://repoverlay.tylerbutler.com/guides/profiles/
repoverlay copilot and repoverlay claude now accept --profile more than once, letting you stack several profiles into one ephemeral agent session. Each profile is applied and locked independently, and all of them are torn down automatically when the agent exits. If a profile fails to apply, any profiles already applied in that run are rolled back so the repository is left clean.
Overlays placed in a source's reserved @global/ namespace now apply to every repository, regardless of its git remote. Create one with repoverlay create <name> --global, and apply it by its bare name (for example repoverlay apply dotfiles). Global overlays are listed for every repo under a Global heading in repoverlay browse and displayed as */<name>. Repo-scoped overlays still take precedence over a global overlay of the same name.
Applying a profile for the Claude harness now writes its instructions entries into a profile-keyed managed region of the repository's CLAUDE.md, mirroring the existing Copilot AGENTS.md behavior. Existing user content is preserved, and removing the profile strips only its own region. Previously Claude applies skipped instructions with a warning.
edit, sync, and create now validate the target repository through the same shared check as apply, so all commands report Target is not a git repository: <path> instead of three slightly different variants.
Overlay sources now skip the reserved @global and @library directories instead of treating them as org/repo/name overlays, and neither can be addressed as a literal org, repo, or overlay name. This lets clients tolerate sources that use the upcoming global-overlay feature.
In symlink mode, a directory declared in an overlay is linked into the target repo as-is, so a malicious overlay could embed a symlink exposing arbitrary host paths (for example .claude/evil -> ~/.ssh) through the target repository. Directories are now vetted before anything is applied: applying fails with a "Symlink escape detected" error and the target repo is left untouched. Symlinks that stay within the overlay directory continue to work. Copy mode already had equivalent protection.
Internal git checkout and git fetch invocations now terminate argument parsing with --, matching the existing clone invocations. Refs were already validated to never begin with -; this is defense-in-depth so a ref that also names a file in the repository cannot be misinterpreted by git.
repoverlay update truncated commit hashes to 7 bytes with a byte-range slice, which would panic if persisted overlay state held a commit string shorter than 7 bytes (for example a corrupt or hand-edited state file). The truncation is now length-checked, matching the rest of the codebase.
JSON conflicts between overlays now fail by default like other cross-overlay conflicts. Pass --merge to opt into deep merging, and repoverlay rejects symlinked merge targets instead of following them.
repoverlay restore still attempts every saved overlay, but now summarizes successes and failures and exits non-zero if recovery was incomplete.
BREAKING: Source configuration now accepts documented local path syntax plus https://, ssh://, git@, GitHub shorthand, and bare owner references. Unsupported schemes such as file://, ftp://, and http:// fail immediately.
repoverlay status --json now includes schema_version: 1 and serializes through explicit stable DTOs instead of internal state types.
Remove deprecated hidden CLI compatibility syntax
BREAKING: Remove create-local, list, edit --add/--remove/--interactive, and cache clear. Use create --output, browse, edit add/remove, plain edit, and cache remove --all instead.
Validate managed paths consistently, reject symlink-ancestor escapes, and use atomic writes for persistent outputs including config, git exclude files, state markers, and merged JSON.
Local directory sources now auto-detect flat overlay folders, including dotfile-only overlays.
This release refreshes project release metadata only. There are no changes to repoverlay behavior, commands, or generated binaries.
The CLI help now points interactive users to repoverlay browse for discovering and applying overlays, while repoverlay apply is framed as the scripting and advanced workflow. Non-interactive browse output also tells users to run it in a terminal.
Use repoverlay move --to source:<name> to move an overlay into a configured overlay source. The target repository path is detected from the current git remote, or can be set explicitly with --target-repo org/repo.
A source file can now map to multiple target paths using duplicate keys in the mappings section:
mappings = .editorconfig = .editorconfig .editorconfig = packages/frontend/.editorconfig
This copies the source file to every listed target. Single-value mappings continue to work as before. All targets are tracked in the overlay state file, so remove cleans up every copy.
Previously, corrupted or unparseable .ccl state files were silently ignored. Now repoverlay logs a warning message identifying the problematic file before skipping it, making it easier to diagnose configuration issues.
prompt_save_source() loaded only global config when checking for duplicates, so it could prompt the user to save a source that was already configured at the repo level. The duplicate-check now loads the merged config (global + repo-local) while still writing saves to the global config file.
Previously, if reading either the existing or overlay file failed in show_file_diff, the error was silently swallowed and the file was treated as empty. Now the function logs a warning and prints a user-visible warning message identifying the problematic file before returning early.
OverlayName previously only checked for forward slashes via debug_assert, meaning invalid names with path separators (including backslashes) could slip through in release builds. Added OverlayName::try_new which returns an error for names containing / or \, and updated all user-input paths to use it.
State file writes previously used a non-atomic truncate-then-write pattern, which could leave corrupted or empty files if the process was interrupted mid-write. State files are now written to a temporary file in the same directory and atomically renamed into place using tempfile::NamedTempFile.
Three-part references like org/repo/overlay failed with "Overlay repository not configured" when the source was configured at the repo level via repoverlay source add. Repo-local sources in .repoverlay/config.ccl are now correctly loaded during resolution.
When applying via a GitHub URL that matches a configured source, repoverlay upgrades to editable overlay repo mode. This matching now includes repo-local sources, not just global ones.
Previously, create --into library --source <path> placed the overlay into the source repo's library instead of the target repo's (cwd). Added --target parameter to the create command for explicit target specification, consistent with other commands like move and switch.
Adds a new top-level move command that relocates an overlay's source files between locations while preserving applied state. Supports moving to the in-repo library (--to library), to a filesystem path (--to /path/to/dir), or renaming on move (--name).
The operation is interrupt-safe: files are copied to the destination, state is updated, symlinks are re-created pointing to the new location, then the source is deleted. If interrupted, the worst case is a duplicate that can be cleaned up manually.
Supports --force to overwrite existing destinations and --dry-run to preview changes.
Overlays can now inherit files from other library overlays using two new repoverlay.ccl sections. Multi-level chains are supported with cycle detection.
extends inherits all files from a parent overlay. The child's own files take precedence on conflict, making this ideal for creating specialized variants of a base overlay:
extends =
overlay = base-config
Use case: a claude-config-strict overlay that extends claude-config and overrides just the CLAUDE.md file while inheriting everything else.
includes cherry-picks specific files from other overlays, useful when you only need a few shared files without full inheritance:
includes =
overlay = shared-dotfiles
files =
.editorconfig
.prettierrc
Use case: multiple overlays that each need the same .editorconfig from a shared dotfiles overlay, without duplicating the file.
Both features are restricted to library overlays.
When --yes is used and no AI config files are found, create now falls back to tracked config files (.envrc, .gitignore, .vscode/settings.json, etc.) instead of bailing. The auto-select priority is: AI configs first, tracked configs second, then error with a helpful message.
edit add with a short-form overlay name (e.g., my-overlay) no longer requires a git remote origin. Previously, the command called detect_target_repo() to resolve org/repo for an error message hint, but this is unnecessary for the actual operation. Now uses the same inline name extraction as edit remove and edit --interactive.
Directories removed via edit remove no longer reappear when the overlay is removed and reapplied. The ExcludedFile struct now tracks whether an exclusion is for a file or directory, and directory exclusions match descendant paths. Also fixes trailing-slash handling (.vscode/ is now treated the same as .vscode) and makes external state backup failures a hard error to ensure exclusions are always persisted.
Apply, remove, and switch no longer abort when .git/info/exclude cannot be updated (e.g. in codespace worktrees with non-portable absolute paths). A warning is shown and the operation continues — overlay files may appear as untracked in git status.
Git reads info/exclude from the shared .git/ directory, not the worktree-specific $GIT_DIR. Overlay files applied in worktree checkouts would show as untracked because exclude entries were written to the wrong location. Now uses git rev-parse --git-path to resolve the correct path.
You can now store overlays directly in your repository at .repoverlay/library/, making them shareable with your team via version control. The new library subcommand group provides full lifecycle management:
-
library list— see what's in the library -
library import <path-or-name>— add an overlay (accepts filesystem paths or applied overlay names) -
library export <name> <dest>— copy an overlay out of the library -
library remove <name>— delete from the library
Library overlays integrate throughout the tool:
-
apply my-overlayresolves from the library first (highest priority) -
apply my-overlay --from @librarytargets the library exclusively -
create --into librarycreates overlays directly in the library -
browseincludes library overlays alongside configured sources -
browseworks with library-only repos — no external sources required
The library path defaults to .repoverlay/library/ but is configurable via library_path in your repo's repoverlay.ccl config. .gitignore is automatically updated to ensure library contents are tracked by git.
Applied overlays now display relative timestamps (e.g. '2 days ago') in the browse selection UI instead of a plain 'already applied' label, making it easier to see how recently each overlay was synced.
Local directory sources no longer require org/repo/name nesting. Flat directories are auto-detected: a directory with overlay files is treated as a single overlay, and subdirectories are each treated as separate overlays.
Updated help text, README, and CLI reference to recommend browse as the primary entry point for interactive use. The apply command remains available for scripting and power users. Improved the error message when no sources are configured.
The --include flag on create now accepts glob patterns (e.g., *.md, .claude/**) in addition to exact file paths. Globs are expanded relative to the source repository root using standard glob syntax.
create now discovers tracked config files (e.g., .envrc, .vscode/, justfile, Cargo.toml, Dockerfile) in addition to AI configs, gitignored, and untracked files. Detection uses an exclusion-based heuristic that filters out source code, documentation, and media rather than trying to allowlist config patterns. Also fixes output path to create overlay at output/<name>/ subdirectory.
Skip transient tool state directories (worktrees/, todos/) during AI config directory walking, reducing discovered files from ~59K to ~22 on repos with large .claude/ directories. Also fix O(n²) ancestor traversal in the selection UI with a pre-built parent lookup map, and add a progress spinner with Ctrl+C support for git clone/pull operations.
The edit add command now correctly handles directories (e.g., .claude/commands) by using recursive directory copy, directory symlinks, and EntryType::Directory in overlay state. Rollback logic also properly restores directories on failure.
edit add no longer requires a git remote origin when working with overlays applied from local paths. Remote detection is deferred to only when needed for overlay repo auto-commit.
Files removed via edit remove now stay removed when an overlay is removed and reapplied. Exclusions are tracked in overlay state and persisted in the external backup so they survive the full lifecycle.
restore no longer errors when an overlay's state exists but its symlinked files have been deleted. Since restore's purpose is to re-create missing files, it now always forces past the "already applied" check.
Register directories as overlay sources using source add ./path. Local sources are stored in a per-repo config file (.repoverlay/config.ccl), which is automatically git-excluded. Paths must start with /, ./, or ~ to be recognized as local (otherwise treated as git URL/shorthand). Local sources skip cloning and caching — overlays are read directly from the filesystem. Use source list and source remove to manage both global git sources and repo-local sources.
The switch command now supports --dry-run to preview what would change without making modifications, consistent with apply, remove, restore, and update.
sync --all no longer fails when only locally-applied overlays are present. The overlay repo manager is now lazily initialized, only created when a syncable overlay is encountered. Local and GitHub overlays are skipped with a warning message.
When syncing a single overlay by name in a fork repository, the sync command detected the org/repo from the git remote (e.g., alexvy86/FluidFramework) instead of using the org/repo saved in the overlay state (e.g., microsoft/FluidFramework). This caused sync to fail with "does not exist in overlay repo" because the fork's org/repo path doesn't exist in the overlay repo. Now uses the org/repo from the saved state, matching the behavior of sync --all.
When applying an overlay via a GitHub URL that matches a configured source, resolve it as an overlay repo (editable and syncable) instead of a read-only GitHub source. URLs with an org/repo/name subpath redirect to three-part resolution; bare repo URLs use interactive browse mode with the matched source.
The single-name sync path was missing the try_upgrade_github_source() call that was added to sync --all, edit, and edit add in #171. This caused GitHub-sourced overlays to be rejected as non-syncable instead of being lazily upgraded to editable overlay repo sources.
Fixed error output formatting to use Display instead of Debug format. Added SIGPIPE signal handling so piped output to commands like head exits cleanly without "Broken pipe" errors.
The --add and --remove flags used greedy num_args = 1.. parsing, which consumed trailing arguments and required the overlay name to appear before any flags. This was confusing and the help text couldn't convey the constraint clearly. The edit command now uses proper subcommands:
repoverlay edit add my-overlay file1.txt file2.txt repoverlay edit remove my-overlay oldfile.txt repoverlay edit my-overlay # interactive file selection repoverlay edit # select overlay then edit interactively
Running edit with no overlay name now presents an interactive overlay picker (auto-selects when only one overlay is applied). The old --add/--remove/--interactive flags still work but are hidden from help and print a deprecation warning.
When adding files to an applied overlay, the git exclude section was rewritten with only the newly added files, silently removing entries for previously managed files. This caused those files to reappear in git status after the add operation. Rebuilt the full exclude list from overlay state, matching the pattern already used by edit --remove.
The --add and --remove flags accept multiple values and greedily consume trailing arguments. Running edit --add file.txt name fails because name is parsed as a second file. Updated help text to document the required argument order.
All commands that read from the overlay repo or GitHub cache now pull the
latest by default. The --update flag is replaced with --no-update to
opt out (e.g., for offline use). Affected commands: apply, browse,
list, switch, create, and sync.
BREAKING: The --update flag has been removed. Use --no-update to
skip syncing.
Use --interactive (-i) to be prompted for each file conflict during apply, restore, and update. When a conflict is detected, you can choose to overwrite the file, skip it, view a diff, or abort the operation. The --force flag can also be written as --overwrite.
When apply resolves a username or owner/repo reference for the first time, it now prompts the user to save it as a configured source for future use. The prompt is skipped if the source is already configured or in non-interactive mode.
browse now accepts an optional source argument (GitHub username, owner/repo, or URL) to fetch and browse overlays without adding a persistent source. Existing behavior using configured sources is unchanged when no argument is provided.
The create command now automatically applies the overlay to the source
repository after creating it. Files are replaced with symlinks, overlay
state is saved, and git exclude is updated. Both local and overlay-repo
modes are supported. Dry-run mode skips the apply step.
add_files_to_overlay is now source-type-aware and includes a rollback
mechanism. Local overlays copy files to the overlay directory; GitHub
overlays are rejected with a clear error. If any operation fails mid-way,
all completed operations are rolled back to prevent partial state.
Running update on overlays from an overlay repo incorrectly displayed
messages meant for local sources. Each source type now shows the correct
label and update behavior.
Overlays from local directory sources were incorrectly handled through the remote overlay path, causing apply and dry-run to fail. Local sources now resolve correctly.
The deprecated overlay_repo configuration field and automatic migration
from the old format have been removed. Users must use the sources
configuration format. The OverlayRepoConfig struct is retained for use
by the sources system.
Removed hidden/deprecated command variants that have been replaced:
add (use edit --add), publish (use create), list (use browse),
and create-local (use create --local).
The interactive overlay picker now displays already-applied overlays as dimmed, non-selectable entries so users can see what's active without leaving the selection UI.
When running in an interactive terminal, browse now presents a multi-select picker to choose overlays and applies them directly. Already-applied overlays appear disabled. Non-interactive mode (piped output or --no-interactive) continues to list overlays as text. Also adds --target, --no-interactive, and --dry-run flags.
The remove command now uses an interactive multi-select picker with keyboard navigation, search filtering, and select-all toggling, replacing the old numbered-list prompt. Multiple overlays can be removed at once.
Outputs applied overlay state as structured JSON including overlay name, source info, applied timestamp, and per-file status (ok or missing). Useful for scripting and CI integration (e.g. repoverlay status --json | jq ...). Works with --name filter to output a single overlay.
Exits with code 0 if overlays are applied, 1 if none. Produces no output. Useful for conditional scripts (e.g. if repoverlay status -q; then ...).
Only syncs overlays sourced from the overlay repo. Overlays from local or GitHub sources are skipped with a warning. Supports --dry-run.
Overlays applied via two-part browse mode (e.g., repoverlay apply owner/repo-overlays) are stored with a GitHub source type rather than an OverlayRepo source type. The sync command now detects these by checking if the GitHub URL matches a configured overlay source and the subpath contains a valid org/repo/name reference.
When browsing or selecting overlays, the current repository is auto-detected from git remotes (origin and upstream). Matching overlays are shown first; non-matching overlays are labeled "different repo" in the interactive picker. Text listing filters to matching overlays by default. Use --show-all to see all overlays.
Full docs site with installation instructions, quick start guide, concept explanations (overlay repos, sources, fork inheritance, configuration), usage guides, and CLI reference.
Several commands have been renamed or consolidated for consistency. The old names still work but are hidden from help output and print a deprecation warning. They will be removed in 1.0.
list → browse — Browse available overlays from the overlay repository. Flags (--filter, --update) are unchanged.
create-local → create --output <path> — Local overlay creation is now a mode of the create command. Pass --output to write to a local directory instead of the overlay repository; the overlay name becomes optional in this mode.
cache clear → cache remove --all — Cache removal is unified under cache remove. Use --all to clear everything, or pass a specific owner/repo to remove a single cached repository.
publish → create — Overlay publishing is now handled by create, which auto-detects the target repository from the git remote or accepts an explicit org/repo/name path.
add → edit --add — Adding files to an existing overlay is now a flag on the edit command. Use --add (repeatable) to include new files and --remove to drop files in a single invocation.
Malicious overlay repositories could include symlinks pointing outside their directory tree (e.g., to /etc/passwd). The copy operation now checks each entry with symlink_metadata and rejects any symlink whose target resolves outside the source root. Also adds a recursion depth limit (64) to prevent stack overflow from circular symlinks.
Only https://, ssh://, and git@ URLs are now accepted for overlay repository sources. This prevents file:// and other local schemes from being used to read files from the host filesystem. URLs starting with - are also rejected to prevent git flag injection.
The org, repo, and overlay name components used to construct filesystem paths are now validated to reject .., /, \, and leading . characters, preventing path traversal attacks via crafted overlay references.
-
Deep merge
.jsonfiles during overlay applicationResolve
.jsonfile conflicts automatically instead of failing. When--mergeis enabled (via CLI flag orREPOVERLAY_MERGEenv var), JSON files are recursively deep merged — objects merge key-by-key with overlay values winning, arrays and scalars use overlay values directly, and type mismatches are logged with full dotted key paths. Merged files are tracked with a newMergedlink type in overlay state for correct cleanup on remove/update.
-
Reload overlay targets after removing a conflicting overlay during re-apply
When
--forceremoved an existing overlay to make room for a re-apply, the in-memory target set was stale, causing subsequent conflict checks in the same batch to see phantom conflicts. The target set is now reloaded after each forced removal.
-
Add
editcommand for modifying existing overlaysNew
editcommand with--add,--remove, and--interactiveflags for modifying applied overlays.--addadds files to an overlay,--removeremoves specific files without removing the whole overlay, and--interactivere-runs the file selection UI with currently-applied files pre-selected. The existingaddcommand is deprecated in favor ofedit --add.
-
editdefaults to interactive file selection when no flags givenRunning
repoverlay edit <name>without--add,--remove, or--interactivein an interactive terminal now launches interactive file re-selection automatically. Non-interactive environments are unaffected.
-
removedefaults to interactive selection when no arguments givenRunning
repoverlay removein an interactive terminal now launches the interactive overlay selection instead of printing a usage error. Non-interactive environments (CI, piped stdin, TERM=dumb) are unaffected.
-
Reject git refs starting with
-to prevent flag injectionSource URLs with refs like
--upload-pack=evilcould be passed through to git commands as flags.GitRef::from_strnow rejects any ref beginning with-andwith_ref_overridepropagates the error instead of panicking via.unwrap(). -
Validate overlay mapping destinations against path traversal
Overlay configs with mappings like
secret.txt = ../etc/passwdcould write files outside the target repository. Mapping destinations are now canonicalized and verified to stay within the target directory before any files are copied. Symlinks in overlay sources are also skipped to prevent symlink-based escapes.
- support bare owner names in source URL validation (#89)
- (cli) add --force and --skip-conflicts flags for conflict handling (#36)
- resolve overlay repo config from sources instead of legacy field (#86)
- add badges and streamline README (#87)
- (deps) bump taiki-e/install-action from 2.67.18 to 2.67.26 (#71)
- (deps) bump release-plz/action from 0.5.124 to 0.5.126 (#72)
- validate and expand source URLs at deserialization time (#83)
- auto-migrate legacy overlay_repo config to sources format (#80)
- support multi-overlay selection in browse mode apply (#77)
- add tree navigation with multi-level hierarchy for create UX (#75)
- (deps) bump tiny-update-check from 0.1.0 to 1.0.0 (#73)
- gitignore
- configure repo policies
- bump deps to address vulnerabilities
- prevent restore from re-applying explicitly removed overlays (#67)
- add Marp presentation slides for repoverlay (#63)
- support git worktrees for exclude file management (#65)
- (cli) add update notifications (#54)
- (cli) add shell completions command (#51)
- (ci) add PR binary size comparison workflow (#52)
- (sources) add unified overlay syntax (#48)
- (cli) improve version string format for local builds (#46)
- (cli) add dry-run flags, help headings, and create-local command (#45)
- (sources) add multi-source overlay sharing (#44)
- add debug logging and documentation improvements (#34)
- (resolve) handle nested overlay repo structure correctly (#50)
- (ci) checkout PR branch before pushing metrics updates
- (deps) bump dawidd6/action-download-artifact from 8 to 14 (#57)
- add workflow to close dependabot PRs for generated files
- cargo update
- (talk) restructure to apply-first flow with unified syntax (#47)
- (deps) bump the actions group with 5 updates (#42)
- (deps) bump the rust-deps group with 2 updates (#43)
- update sickle to pick up fixes
- add talk outline and Marp slide deck
- enhance justfile with organized recipes and bloat profile (#41)
- add reusable actions and improved workflows (#40)
- add Cargo.toml improvements for lints and profiles (#38)
- add conventional commit enforcement tooling (#39)
- add rust toolchain and formatting configuration (#37)
- add cargo binstall command
- (overlay) add directory symlink support (#31)
- (cli) add subcommand to add files to existing overlays (#30)
- simplify overlay publishing workflow (#16)
- (create) add interactive file selection UI with category filters (#17)
- use ~/.config for config and default create to overlay repo (#12)
- improve terminal interactivity detection
- use output_dir for create command default path (#15)
- improve code coverage for overlay_repo and selection modules (#21)
- improve code coverage for cache, lib, and main modules (#20)
- (deps) upgrade dependencies (#19)
- simplify state format using sickle's improved serde support (#11)
- improve documentation structure and clarity (#10)
- document decision to use git CLI over git library
- extract library crate and reorganize tests (#8)
- add overlay repository management with CCL config format
- add interactive mode for overlay creation
- add smart discovery for overlay creation
- add create and switch commands
- coverage workflow builds binary before running tests
- resolve clippy warnings and coverage workflow issues
- improve test coverage for cache, config, github, and overlay_repo modules
- add code coverage, security audit, and documentation checks
- extract helper functions to reduce code duplication
- use PAT for release-plz to trigger release workflow
- fix release-plz config to create tags for cargo-dist
- add automatic tag creation on release PR merge
- add installation methods to README
- add cargo-dist for binary releases and Homebrew distribution
- add GitHub repository overlay support
- add multi-overlay support
- initial repoverlay CLI implementation
- build binary before running tests
- fix workflow action names and release-plz config
- add README, DEV guide, and Claude Code instructions
- add CI/CD workflows and release automation