A powerful interactive tool for managing configuration file links between Git worktrees.
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.
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
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
# 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 linkNEW: 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 wtlinkInteractive 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)
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.
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).
When running wtlink manage, you'll decide what to do with each file. Press ? anytime for help!
- 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
- 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
- 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.
| Key | Action |
|---|---|
↑ ↓ |
Move cursor up/down through the file list |
→ |
Drill into a directory (navigate deeper) |
← |
Go back to parent directory |
| 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!
| 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 |
╔═══════════════════════════════════════════════════════════════════════════╗
║ Worktree Config Link Manager ║
╚═══════════════════════════════════════════════════════════════════════════╝
✓ Will Link: 5 ◎ Commented: 3 ✗ Skipped: 127 ⋯ Undecided: 865
Viewing: Undecided | Layout: Hierarchical
Numbers 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
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.
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.txt
Note: 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.json
Symbols:
▶= 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
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 cancel
Using the interactive main menu (recommended for beginners):
-
Launch the main menu:
npx wtlink
-
Select "Manage config manifest" from the menu
-
Press
?to see help - understand what each action does -
Navigate 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
Using direct commands (for experienced users):
-
Run manage:
npx wtlink manage
-
Follow steps 3-8 from the main menu workflow above
-
Link the files:
npx wtlink link
-
Show specific categories:
npx wtlink manage
-
Press
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.
# 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.
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)
# 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
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 all
Important: The manifest is always stored in the main worktree root, not in linked worktrees. This ensures all worktrees share the same configuration.
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.
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!
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)
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
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
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.
Cause: Brand new repository with no commits.
Fix: Make at least one commit first:
git add .
git commit -m "Initial commit"Check:
-
Files are active in manifest (no
#prefix):cat .wtlinkrc
-
Files exist in source worktree:
ls /path/to/source/.vscode/settings.json
-
Files are git-ignored:
git check-ignore .vscode/settings.json
-
Manifest is in the main worktree root (not a linked worktree)
Solution: Run wtlink manage again:
- Discovers new files
- Cleans up stale entries
- Preserves existing decisions
Symptom: Tool crashes with ENOBUFS error
Cause: Extremely large number of ignored files (10,000+)
Status: Fixed in latest version (50MB buffer)
cd tools/wtlink
npm install
npm run buildcd 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
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 file
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.
cd tools/wtlink
npm linkThis makes wtlink available as a global command.
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.
This project uses semantic-release for automated versioning and publishing.
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
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.
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
MIT © 2025 Chris Sena
See LICENSE file for details.