wtlink
v3.0.1
Published
Interactive CLI tool for managing configuration file links between git worktrees. Share config files while keeping build artifacts separate.
Downloads
7
Maintainers
Readme
wtlink - Worktree Config Link Manager
A powerful interactive tool for managing configuration file links between Git worktrees.
Version Compatibility
v3.0.0 - Process Change (No User Impact)
Important: v3.0.0 contains a BREAKING CHANGE to the release process, not to the user-facing API or CLI.
For end users: ✅ No action needed - all commands, features, and functionality work exactly the same as v2.0.0.
What changed: The project now uses automated semantic versioning and releases. This is a breaking change for maintainers/contributors who can no longer manually bump versions, but does not affect how you use wtlink.
v2.0.0 - Breaking Changes
If upgrading from v1.x, be aware of these changes:
Manifest filename changed:
.worktree-link-configs.txt→.wtlinkrc- Old manifests will NOT be automatically migrated
- Run
mv .worktree-link-configs.txt .wtlinkrcto migrate manually - Or run
npx wtlink manageto create a new.wtlinkrc
Backups off by default: The
--backupflag is now required to create backups- Old behavior: Always created
.bakfiles - New behavior: Only creates backups when
--backup/-bflag is used - Use
npx wtlink manage --backupif you want backups
- Old behavior: Always created
Manifest location fixed: Manifest is now ALWAYS stored in the main worktree root
- Old behavior: Each worktree could have its own manifest (bug)
- New behavior: All worktrees share one manifest in the main worktree
- This fixes a critical bug where linked worktrees created separate manifests
Terminology change: "Will Link (Commented)" → "Will Track (Commented)"
- More accurate description of what commented entries do
- No action needed - just a UI/documentation change
What Does It Do?
When working with Git worktrees, you often want to share certain configuration files (like .vscode/settings.json, .editorconfig, etc.) across all worktrees while keeping build artifacts and other generated files separate.
wtlink helps you:
- Discover all ignored files in your repository
- Decide which files should be linked between worktrees
- Manage a manifest file (
.wtlinkrc) that tracks these files - Link files automatically using hard links
Quick Start
# Install dependencies
cd tools/wtlink
npm install
# Build the tool (auto-runs after install via prepare script)
npm run build
# Interactive main menu - recommended for first-time use
npx wtlink
# Or run commands directly:
# Discover and manage files
npx wtlink manage
# Link files from main worktree to feature branch (auto-detects worktrees)
npx wtlink linkCommands
wtlink (no arguments)
NEW: Interactive main menu that provides a guided workflow through all commands.
Features:
- 📋 Menu options: Manage, Link, Validate, Help, Exit
- 🔄 Guided workflow: Prompts to link after managing
- ⌨️ Easy navigation with arrow keys
- 📖 Built-in help screen
- 💾 Automatic save and exit
When to use:
- First-time setup and learning the tool
- Complete workflow from discovery to linking
- When you're not sure which command to run
npx wtlinkwtlink manage
Interactive file selection interface to manage your worktree config manifest.
Features:
- 🔍 Discovers all git-ignored files
- 📁 Hierarchical directory navigation
- ⚡ Instant keyboard actions
- 👁️ Toggle visibility of decided items
- 🔄 Pre-populates decisions from existing manifest
- ⚠️ Detects and handles stale manifest entries
- ❓ Built-in help system
Stale Entry Detection:
When you run wtlink manage, the tool checks for files in your manifest that:
- No longer exist (deleted files) - prompts to remove, comment as
# DELETED, or leave unchanged - Are now tracked by git (not ignored anymore) - prompts to remove (recommended), comment as
# TRACKED, or leave unchanged
This prevents linking files that could cause git conflicts or don't exist anymore.
Pre-populated Decisions: Files already in the manifest are pre-populated in the interactive view:
- Active entries → ✓ Will Link
- Commented entries → ◎ Will Track (Commented)
- New files → ⋯ Undecided
Options:
--non-interactive,-n: Non-interactive mode (new files added as comments)--clean,-c: Clean mode (stale entries automatically removed)--dry-run,-d: Preview changes without writing--backup,-b: Create backup of manifest before updating (default: false)
wtlink link [source] [destination]
Creates hard links for all files listed in the manifest.
Auto-detection:
- If you omit
sourceanddestination, the tool will auto-detect them - Destination defaults to current worktree
- Source inferred from
git worktree list(prefersmain,master, ordevelop)
Conflict Detection: Before creating any links, the tool scans for conflicts:
- ✅ Safe: Destination file doesn't exist (ready to link)
- 🔗 Already linked: File is already correctly linked (skipped)
- ⚠️ Conflict: Different file exists at destination
Interactive Conflict Resolution:
When conflicts are detected (and --yes is not used), you'll be prompted to resolve them:
- Bulk resolution: Apply the same action to all conflicts
- Individual resolution: Decide each conflict separately
Resolution Actions:
- Replace - Delete destination file and create link (overwrites existing)
- Ignore - Keep destination file as-is, don't create link
- Remove from manifest - Remove file from manifest (won't link now or in future)
After resolving conflicts, you'll see a summary and final confirmation before any changes are made.
Options:
--type symbolic: Create symbolic links instead of hard links--dry-run,-d: Preview links without creating them--yes,-y: Skip confirmation prompt and auto-replace all conflicts
Example:
# Auto-detect source and destination (prompts for confirmation)
npx wtlink link
# Preview what will be linked (no confirmation needed)
npx wtlink link --dry-run
# Skip confirmation prompt (useful for automation)
npx wtlink link --yes
# Or specify explicitly
npx wtlink link ~/projects/syrf ~/projects/syrf-feature-branchConflict Resolution Example:
⚠️ Found 3 conflicting files
config/
- local-settings.json
Resolution Options:
R - Replace destination file (delete existing, create link)
I - Ignore (keep destination file as-is, don't link)
M - Remove from manifest (won't link now or in future)
How do you want to resolve these conflicts?
> Resolve all conflicts the same way
Resolve each conflict individually
═══════════════════════════════════════
Conflict Resolution Complete!
═══════════════════════════════════════
Summary:
⚠ Replace: 2 files (will overwrite and link)
ℹ Ignore: 1 file (will skip, keep destination)
✓ Safe: 10 files (no conflict)
From: ~/source/repos/syrf-monorepo
To: ~/source/repos/syrf-feature
Type: hard links
Proceed with linking 12 files? (2 will overwrite existing files)Confirmation Prompt:
By default, wtlink link will show a confirmation prompt before creating links:
Found 15 files to link
From: ~/source/repos/syrf-monorepo
To: ~/source/repos/syrf-feature
Type: hard links
Proceed with linking? (y/n)The confirmation is automatically skipped when using --dry-run or --yes.
wtlink validate [source]
Validates that manifest entries exist and are properly ignored by git.
Checks:
- Manifest exists and has no duplicates
- Listed files exist in source worktree
- All files are ignored by Git
Example:
npx wtlink validateExits with non-zero status on validation failure (CI-friendly).
Understanding Actions
When running wtlink manage, you'll decide what to do with each file. Press ? anytime for help!
✓ Will Link (Press A)
- What it does: File will be actively linked between worktrees
- Manifest format:
path/to/file.json(no prefix) - When to use:
- Configuration files (
.vscode/settings.json,.editorconfig) - Shared development tools config
- IDE workspace settings you want consistent
- Configuration files (
Example manifest entries:
.vscode/settings.json
.editorconfig
.prettierrc◎ Will Track (Commented) (Press C)
- What it does: File is tracked in manifest but disabled (won't be linked)
- Manifest format:
# path/to/file.json(with#prefix) - When to use:
- Files you might want to link later
- Documentation of potential linkable files
- Testing configuration before enabling
Example manifest entries:
# .vscode/launch.json
# .gitconfig✗ Won't Link (Press S)
- What it does: File is completely ignored (not added to manifest)
- Manifest format: (not present in manifest)
- When to use:
- Build artifacts (
bin/,obj/,node_modules/) - Temporary files
- IDE-specific files you don't want to track
- Cache directories
- Build artifacts (
Result: These files won't appear in the manifest at all.
Interactive UI Guide
Navigation Keys
| Key | Action |
|-----|--------|
| ↑ ↓ | Move cursor up/down through the file list |
| → | Drill into a directory (navigate deeper) |
| ← | Go back to parent directory |
Action Keys (Instant - No Confirmation!)
| Key | Action | Color | Effect |
|-----|--------|-------|--------|
| A | Will Link | 🟢 Green | File will be linked (active) |
| C | Will Track (Commented) | 🔵 Blue | File tracked but disabled |
| S | Won't Link | 🟡 Yellow | File ignored (not in manifest) |
| Q | Quit | 🔴 Red | Save and exit |
Note: Actions happen instantly when you press the key - the item disappears and counts update!
View Toggle Keys
| Key | Action | Description |
|-----|--------|-------------|
| 0 | Toggle Undecided | Show/hide undecided files (on by default) |
| 1 | Toggle Added | Show/hide files marked "Will Link" |
| 2 | Toggle Tracked | Show/hide files marked "Will Track (Commented)" |
| 3 | Toggle Skipped | Show/hide files marked "Won't Link" |
| V | Toggle View Mode | Switch between hierarchical and flat view |
| ? | Toggle Help | Show/hide full help panel |
UI Elements Explained
Status Header
╔═══════════════════════════════════════════════════════════════════════════╗
║ Worktree Config Link Manager ║
╚═══════════════════════════════════════════════════════════════════════════╝
✓ Will Link: 5 ◎ Commented: 3 ✗ Skipped: 127 ⋯ Undecided: 865
Viewing: Undecided | Layout: HierarchicalNumbers are padded to 4 digits (supports up to 9999 items) to prevent layout shifting as counts change.
- Will Link: Files that will be actively linked when you run
wtlink link - Commented: Files tracked in manifest but won't be linked (disabled with
#) - Skipped: Files completely ignored (not in manifest at all)
- Undecided: Files you haven't made a decision on yet
Action Hint Panel
A permanent info panel (2 lines) appears above the file list. When you select a directory with undecided files, it shows helpful information:
ℹ node_modules — 1234 undecided files inside
When you select a file or a folder without undecided files, the panel remains empty to keep the layout stable:
This fixed-height panel prevents the file list from shifting up/down as you navigate.
File List
In hierarchical mode, folders appear first (alphabetically), then files (alphabetically).
Example (default view - hierarchical):
▶ ⬆️ .. (go back)
📁 .idea (20 files: 20 undecided) [auto-ignore]
📁 deploy (200 files: 150 undecided, 30 added, 20 skipped)
✓ 📄 .editorconfig
◎ 📄 .gitconfig
✗ 📄 temp.txtNote: All items are aligned with consistent spacing, even those without status icons.
Example (flat view with V toggle):
▶ 📁 .idea (20 files: 20 undecided) [auto-ignore]
📁 deploy (200 files: 150 undecided, 30 added, 20 skipped)
✓ 📄 .editorconfig
📄 .env
◎ 📄 .gitconfig
📄 package.jsonSymbols:
▶= Current cursor position (blue highlight)📁= Directory (shows state breakdown of all descendants)📄= File✓= Marked "Will Link" (green)◎= Marked "Will Track (Commented)" (blue)✗= Marked "Won't Link" (yellow)= Reserved space (2 spaces) for items without status icons - ensures alignment[auto-ignore]= Common build/cache directory (detected automatically)
Directory State Breakdown:
- Single state:
(20 files: 20 undecided) - Mixed states:
(10 undecided, 5 added, 3 skipped)- shows breakdown of all descendants - Directories appear only once, even if containing items in multiple states
- When view toggles (1/2/3) are active, directories show if they contain matching descendants
Footer
Shows all available key bindings. Active toggles are highlighted!
Nav: ↑↓ select | ← back | → drill-in Actions: A link | C link(commented) | S skip
View: 0 undecided | 1 added | 2 commented | 3 skipped | V flat Help: ? Q save+quit | X cancelCommon Workflows
First-Time Setup (Recommended: Main Menu)
Using the interactive main menu (recommended for beginners):
Launch the main menu:
npx wtlinkSelect "Manage config manifest" from the menu
Press
?to see help - understand what each action doesNavigate through directories:
- Use
↑↓to move through the list - Press
→on a directory to drill in - Press
←to go back
- Use
Make quick decisions on common directories:
- Navigate to
node_modules/→ pressS(won't link) - Navigate to
bin/→ pressS(won't link) - Navigate to
obj/→ pressS(won't link) - Navigate to
.vscode/→ press→to drill in and decide individually
- Navigate to
Decide on individual files:
.vscode/settings.json→ pressA(will link).editorconfig→ pressA(will link).gitconfig→ pressC(will track commented - document but don't link)
Review your choices:
- Press
0to toggle undecided files (on by default) - Press
1to see all files marked "Will Link" ✓ - Press
2to see all files marked "Will Track (Commented)" ◎ - Press
3to see all files marked "Won't Link" ✗ - Press the number again to hide that view
- Combine filters to see multiple states (e.g., press
0and1to see both undecided and added)
- Press
Save and exit:
- Press
Qto quit and save
- Press
Link the files:
- The menu will ask "Would you like to link configs now?" → select Yes
- Files are automatically linked from main worktree to current worktree
- Review the summary and confirm
First-Time Setup (Advanced: Direct Commands)
Using direct commands (for experienced users):
Run manage:
npx wtlink manageFollow steps 3-8 from the main menu workflow above
Link the files:
npx wtlink link
Reviewing and Editing Decisions
Show specific categories:
npx wtlink managePress
0to toggle off undecided items (focus on decided items)Press
1to see only "Will Link" files- Verify these are what you want linked
- Navigate to any file and press
Cto change to "Will Track (Commented)" - Or press
Sto change to "Won't Link"
Press
2to see "Will Track (Commented)" files- Consider if any should be enabled (press
A) - Or completely remove them (press
S)
- Consider if any should be enabled (press
Press
3to see "Won't Link" files- Double-check you didn't skip anything important
- Press
AorCto add them back
Tip: You can combine filters! Press 0, 1, and 2 together to see undecided, added, and tracked files all at once.
Linking Files Between Worktrees
# Auto-detect (recommended)
npx wtlink link
# Or specify explicitly
npx wtlink link ~/projects/syrf ~/projects/syrf-feature-branchThis creates hard links for all active (uncommented) files in the manifest.
Handling Conflicts During Linking
When linking files, you may encounter conflicts if different files exist at the destination:
Scenario 1: Bulk Resolution (All Same Action)
- Run
npx wtlink link - Tool detects 5 conflicting files
- Choose "Resolve all conflicts the same way"
- Select action:
- Replace all - Overwrite all destination files with links
- Ignore all - Keep all destination files, skip linking
- Remove all from manifest - Clean up manifest and skip these files permanently
Scenario 2: Individual Resolution (Per-File Decisions)
- Run
npx wtlink link - Tool detects 5 conflicting files
- Choose "Resolve each conflict individually"
- For each file, decide:
- Replace - You trust the source version, overwrite destination
- Ignore - Destination has custom changes, keep it
- Remove from manifest - This file shouldn't be linked
After Resolution:
- See summary of all decisions
- Final confirmation before any changes
- Manifest automatically updated if files removed
- Can cancel at any point (Ctrl+C)
Tips:
- Use
--dry-runfirst to see what would be linked - Already-linked files are automatically skipped (no prompt)
- Use
--yesfor automation (auto-replaces all conflicts)
Updating the Manifest
# Re-run manage to discover new files
npx wtlink manageThe tool will:
- Show any new ignored files (marked as "Undecided")
- Preserve your existing decisions from the manifest
- Identify stale entries:
- Deleted files - No longer exist on disk
- Tracked files - No longer git-ignored (could cause conflicts)
- Prompt you to handle stale entries before showing the interactive view
Manifest File Format
The manifest is stored in .wtlinkrc at the repository root (in the main worktree):
# Active files (will be linked)
.vscode/settings.json
.editorconfig
.prettierrc
# Commented files (tracked but disabled)
# .vscode/launch.json
# .gitconfig
# Note: Skipped files don't appear in the manifest at allImportant: The manifest is always stored in the main worktree root, not in linked worktrees. This ensures all worktrees share the same configuration.
Configuration (Coming Soon)
Future versions will support configuration via package.json:
{
"wtlink": {
"manifestFile": ".wtlinkrc",
"linkType": "hard",
"ui": {
"colors": true,
"symbols": "unicode"
},
"autoIgnorePatterns": [
"node_modules/",
"dist/",
"build/"
]
}
}Planned features:
- Custom manifest filename and location
- Default link type (hard vs symbolic)
- UI customization (colors, symbols)
- Auto-ignore patterns for common directories
For now, use command-line flags to customize behavior.
Tips & Best Practices
Common Auto-Ignore Directories
The tool automatically detects and highlights common build/cache directories:
node_modules,bin,obj.git,.vs,.vscode,.ideadist,build,coverage,out,target__pycache__,.pytest_cache,.gradle,vendor
Tip: These are marked with [auto-ignore]. Press S on them to skip entire directories instantly!
Using View Toggles Effectively
Review workflow:
- Press
0to turn off undecided view (focus on decided items) - Press
1to see only "Will Link" files - verify these are correct - Press
2to see "Will Track (Commented)" - consider if any should be enabled - Press
3to see "Won't Link" - make sure you didn't skip anything important
Combining filters:
- You can have multiple toggles active at once
- Example:
0+1shows both undecided and added files - Example:
1+2+3shows all decided files (no undecided) - Press
0to go back to default (undecided only)
Flat View Features
Press V to toggle between hierarchical and flat view modes.
In Flat View:
- All directories and files are shown in a single alphabetically sorted list
- Directories are mixed with files (not separated at the top)
- Each directory shows a state breakdown of all its descendants
- View toggles (1/2/3) control which directories appear:
- Directory appears if it contains any descendants matching active toggle states
- Directory appears once even if it has children in multiple states
- Breakdown shows counts for each state:
(5 added, 3 commented, 2 skipped)
Example Workflow:
- Press
Vto switch to flat view - Press
1to show only "added" items - All directories containing added files appear with breakdown
- Files marked as "added" appear in the list
- Navigate and review all added items
Directory Actions Apply to All Children
When you make a decision on a directory (in both hierarchical and flat view):
- All files inside get the same action
- The directory disappears from the list (since all children are now decided)
- Counts update to reflect all affected files
In Flat View:
- Directories are listed alongside files
- Yellow hint shows "Actions apply to all X files inside"
- Perfect for quickly processing large directories like
node_modules/,bin/,obj/
Example:
- Navigate to
node_modules/(shows "1234 files") - Press
S→ all 1234 files marked "Won't Link" - Directory removed from list
- "Won't Link" count increases by 1234
Getting Help Anytime
Press ? at any time to see the full help panel with:
- Detailed explanation of each action
- What gets written to the manifest
- When to use each option
- View toggle descriptions
Press ? again to close help and return to the file list.
Troubleshooting
"Warning: Could not run git ls-files"
Cause: Brand new repository with no commits.
Fix: Make at least one commit first:
git add .
git commit -m "Initial commit"Files Not Linking
Check:
Files are active in manifest (no
#prefix):cat .wtlinkrcFiles exist in source worktree:
ls /path/to/source/.vscode/settings.jsonFiles are git-ignored:
git check-ignore .vscode/settings.jsonManifest is in the main worktree root (not a linked worktree)
Manifest Out of Sync
Solution: Run wtlink manage again:
- Discovers new files
- Cleans up stale entries
- Preserves existing decisions
Buffer Size Errors
Symptom: Tool crashes with ENOBUFS error
Cause: Extremely large number of ignored files (10,000+)
Status: Fixed in latest version (50MB buffer)
Development
Building from Source
cd tools/wtlink
npm install
npm run buildRunning Tests
cd tools/wtlink
npm test # Run all tests
npm run test:watch # Run tests in watch mode
npm run test:coverage # Run tests with coverage reportTest Coverage:
- File tree building and traversal
- Directory state breakdown calculation
- Flat view directory filtering with view toggles
- Alphabetical sorting of directories and files
- Edge cases and mixed states
Project Structure
tools/wtlink/
├── src/
│ ├── cli.ts # CLI entry point and command router
│ ├── manage-manifest.ts # Interactive file manager (reactive architecture)
│ ├── manage-manifest.test.ts # Tests for pure functions and state logic
│ ├── link-configs.ts # Hard link creation logic
│ └── validate-manifest.ts # Manifest validation
├── dist/ # Compiled JavaScript
├── jest.config.js # Jest test configuration
├── ARCHITECTURE.md # Reactive architecture documentation
├── package.json
└── README.md # This fileArchitecture
The interactive file manager uses a reactive, declarative architecture with high-performance signals:
- @preact/signals-core: Reactive state management with automatic memoization
- Immutable state: All state updates create new objects
- Pure functions: State computation with no side effects
- Single source of truth:
getVisibleItems()prevents duplication - Derived folder states: Folders show all states present in their children
- Smart caching: Computed values only recalculate when dependencies change
Performance benefits:
- Cursor movement: Instant (~0ms) - uses cached results
- Filter toggles: Fast (~50ms) - only recomputes when needed
- Large file trees: No lag even with 1000+ files
Key benefit: Folders can have multiple states (e.g., both "added" and "skipped" children) and appear exactly once in the UI, eliminating duplicate listing bugs.
See ARCHITECTURE.md for detailed design documentation.
Global Installation for Development
cd tools/wtlink
npm linkThis makes wtlink available as a global command.
Adding to Repository Package.json
Add to your repo root package.json:
{
"bin": {
"wtlink": "./tools/wtlink/dist/src/cli.js"
}
}Then run npm install to make wtlink available via npx.
Releases & Versioning
This project uses semantic-release for automated versioning and publishing.
Automated Releases
Every push to the main branch triggers an automated release workflow:
- ✅ Tests and linting must pass
- 🔍 Commit analysis - examines commit messages since last release
- 📈 Version bump - automatically determined from commits:
fix:commits → Patch release (3.0.0 → 3.0.1)feat:commits → Minor release (3.0.0 → 3.1.0)BREAKING CHANGE:→ Major release (3.0.0 → 4.0.0)
- 📝 Changelog - auto-generated and committed
- 📦 npm publish - published to npm registry
- 🏷️ GitHub release - created with release notes
Conventional Commits
This project follows the Conventional Commits specification:
# Patch release (bug fixes)
git commit -m "fix: resolve path resolution issue on Windows"
# Minor release (new features)
git commit -m "feat: add support for symbolic links"
# Major release (breaking changes)
git commit -m "feat!: redesign CLI interface"
# or
git commit -m "feat: redesign CLI interface
BREAKING CHANGE: Command syntax has changed. See migration guide."Commit types:
feat:- New feature (minor version bump)fix:- Bug fix (patch version bump)docs:- Documentation changes (no release)chore:- Maintenance tasks (no release)refactor:- Code refactoring (no release)test:- Test changes (no release)
See CONTRIBUTING.md for complete details on the release process.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
Development Setup:
git clone https://github.com/chrissena/wtlink.git
cd wtlink
npm install
npm run build
npm link # For global testingRunning Tests:
npm test # Run all tests
npm run test:watch # Watch mode
npm run test:coverage # With coverage reportCode Quality:
- Write tests for new features
- Maintain 80%+ test coverage
- Follow existing code style
- Update documentation
License
MIT © 2025 Chris Sena
See LICENSE file for details.
