From 0fb139443c846ad5d1f84e554a2607d096022b23 Mon Sep 17 00:00:00 2001 From: emredursun Date: Thu, 9 Apr 2026 01:24:35 +0200 Subject: [PATCH 1/3] feat: add automatic worktree support via .worktreeinclude and post-checkout hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three-layer worktree support ensures Kit workflows work seamlessly in git worktrees without manual re-initialization: 1. .worktreeinclude — Claude Code native mechanism copies gitignored files 2. post-checkout hook — copies .agent/ and bridge dirs from main worktree 3. --skip-worktree flag — opt-out for users who don't need worktree support New lib/worktree.js module with generateWorktreeInclude() and installPostCheckoutHook(). Hook uses cp -RP for symlink safety and provenance markers for idempotent operation. --- bin/kit.js | 2 + lib/commands/init.js | 41 +++++++ lib/constants.js | 10 ++ lib/updater.js | 11 ++ lib/worktree.js | 177 +++++++++++++++++++++++++++++ tests/unit/worktree.test.js | 215 ++++++++++++++++++++++++++++++++++++ 6 files changed, 456 insertions(+) create mode 100644 lib/worktree.js create mode 100644 tests/unit/worktree.test.js diff --git a/bin/kit.js b/bin/kit.js index c80e561..9729295 100644 --- a/bin/kit.js +++ b/bin/kit.js @@ -99,6 +99,7 @@ ${colors.bright}Options:${colors.reset} (claude,cursor,opencode,codex,vscode,windsurf,all) --skip-ide Skip IDE config generation --skip-commands Skip slash command bridge generation + --skip-worktree Skip worktree support (.worktreeinclude + post-checkout hook) --shared Commit .agent/ to repo (team mode — skip .gitignore) ${colors.bright}Examples:${colors.reset} @@ -458,6 +459,7 @@ const options = { skipIde: args.includes('--skip-ide'), skipCommands: args.includes('--skip-commands'), shared: args.includes('--shared'), + skipWorktree: args.includes('--skip-worktree'), ide: null, path: null, file: null, diff --git a/lib/commands/init.js b/lib/commands/init.js index 10e8d58..6eb7100 100644 --- a/lib/commands/init.js +++ b/lib/commands/init.js @@ -126,6 +126,7 @@ function initCommand(options, ctx) { const targetDir = options.path || process.cwd(); const agentPath = path.join(targetDir, AGENT_FOLDER); const sourcePath = path.join(packageDir, AGENT_FOLDER); + let detectedIDEs = ['claude']; // hoisted for worktree step access // Verify source .agent folder exists if (!fs.existsSync(sourcePath)) { @@ -312,6 +313,7 @@ function initCommand(options, ctx) { const allConfigs = Object.values(bridges).filter(v => v && v.files); const result = writeBridgeConfigs(targetDir, allConfigs, { force: options.force }); + detectedIDEs = bridges.detectedIDEs; log(` ✓ ${result.written.length} command bridges generated (${bridges.detectedIDEs.join(', ')})`, 'green'); if (result.skipped.length > 0) { log(` ⏭ ${result.skipped.length} already exist`, 'yellow'); @@ -327,6 +329,45 @@ function initCommand(options, ctx) { }); } + // Worktree support (.worktreeinclude + post-checkout hook) + if (!options.skipWorktree) { + steps.push({ + label: 'Configuring worktree support', + fn() { + // If --skip-commands was used, detect IDEs independently + if (options.skipCommands && detectedIDEs.length <= 1) { + try { + const { detectIDEs } = require('../command-bridge'); + detectedIDEs = detectIDEs(targetDir); + } catch { /* fallback to default */ } + } + const { generateWorktreeInclude, installPostCheckoutHook } = require('../worktree'); + try { + const result = generateWorktreeInclude(targetDir, detectedIDEs); + if (result.created) { + log(' ✓ .worktreeinclude generated for Claude Code', 'green'); + } else if (result.reason === 'updated') { + log(' ✓ .worktreeinclude updated', 'green'); + } else if (result.reason === 'appended') { + log(' ✓ Entries added to existing .worktreeinclude', 'green'); + } + } catch (err) { + log(` ⚠️ Could not generate .worktreeinclude: ${err.message}`, 'yellow'); + } + try { + const hookResult = installPostCheckoutHook(targetDir); + if (hookResult.installed) { + log(' ✓ post-checkout hook installed for git worktree support', 'green'); + } else if (hookResult.reason === 'user-hook-exists') { + log(' ℹ post-checkout hook exists (user-managed) — skipped', 'cyan'); + } + } catch (err) { + log(` ⚠️ Could not install post-checkout hook: ${err.message}`, 'yellow'); + } + }, + }); + } + // Gitignore configuration if (!options.shared) { steps.push({ diff --git a/lib/constants.js b/lib/constants.js index 822fd7d..e6eb45f 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -71,6 +71,15 @@ const SAFE_COMMAND_NAME = /^[a-z0-9][a-z0-9-]{0,63}$/; /** Provenance header for Kit-generated bridge files */ const KIT_BRIDGE_HEADER = '