From 505340dd8d7ea1e569164ac356be9546b9c70132 Mon Sep 17 00:00:00 2001 From: Donald Silveia Date: Mon, 2 Feb 2026 20:16:58 -0300 Subject: [PATCH 1/3] Skip sync when configs already linked --- src/adapters/amp.ts | 31 ++++++++++++++++++++++++++++++- src/adapters/antigravity.ts | 31 ++++++++++++++++++++++++++++++- src/adapters/claude.ts | 17 +++++++---------- src/adapters/clawdbot.ts | 31 ++++++++++++++++++++++++++++++- src/adapters/codex.ts | 31 ++++++++++++++++++++++++++++++- src/adapters/cursor.ts | 15 +++++++++++---- src/adapters/dotfiles.ts | 18 +++++++++++++++--- src/adapters/droid.ts | 31 ++++++++++++++++++++++++++++++- src/adapters/gemini-cli.ts | 31 ++++++++++++++++++++++++++++++- src/adapters/github-copilot.ts | 31 ++++++++++++++++++++++++++++++- src/adapters/goose.ts | 31 ++++++++++++++++++++++++++++++- src/adapters/kilo.ts | 31 ++++++++++++++++++++++++++++++- src/adapters/kiro-cli.ts | 31 ++++++++++++++++++++++++++++++- src/adapters/opencode.ts | 18 ++++++++++++++++++ src/adapters/roo.ts | 31 ++++++++++++++++++++++++++++++- src/adapters/trae.ts | 31 ++++++++++++++++++++++++++++++- src/adapters/vscode.ts | 15 +++++++++++---- src/adapters/windsurf.ts | 15 +++++++++++---- 18 files changed, 433 insertions(+), 37 deletions(-) diff --git a/src/adapters/amp.ts b/src/adapters/amp.ts index f56822e..00ba756 100644 --- a/src/adapters/amp.ts +++ b/src/adapters/amp.ts @@ -9,6 +9,7 @@ import { createSymlink, ensureDir, exists, + getSymlinkTarget, isSymlink, removeDir, } from "../utils/fs"; @@ -52,7 +53,10 @@ export class AmpAdapter implements AgentAdapter { } isLinked(systemPath: string, repoPath: string): boolean { - return exists(systemPath) && exists(repoPath) && isSymlink(systemPath); + if (!exists(systemPath) || !exists(repoPath) || !isSymlink(systemPath)) { + return false; + } + return getSymlinkTarget(systemPath) === repoPath; } async import(systemPath: string, repoPath: string): Promise { @@ -63,6 +67,20 @@ export class AmpAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + return { + success: true, + message: "Already linked to repo - no import needed", + }; + } + + if (exists(repoPath)) { + return { + success: true, + message: "Configs already in repo - no import needed", + }; + } + ensureDir(repoPath); copyDir(systemPath, repoPath); @@ -80,6 +98,17 @@ export class AmpAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + const target = getSymlinkTarget(systemPath); + if (target === repoPath) { + return { + success: true, + message: "Already linked to repo - no export needed", + linkedTo: repoPath, + }; + } + } + // Remove existing (symlink or directory) if (exists(systemPath)) { if (isSymlink(systemPath)) { diff --git a/src/adapters/antigravity.ts b/src/adapters/antigravity.ts index 0049939..3018f0d 100644 --- a/src/adapters/antigravity.ts +++ b/src/adapters/antigravity.ts @@ -9,6 +9,7 @@ import { createSymlink, ensureDir, exists, + getSymlinkTarget, isSymlink, removeDir, } from "../utils/fs"; @@ -54,7 +55,10 @@ export class AntigravityAdapter implements AgentAdapter { } isLinked(systemPath: string, repoPath: string): boolean { - return exists(systemPath) && exists(repoPath) && isSymlink(systemPath); + if (!exists(systemPath) || !exists(repoPath) || !isSymlink(systemPath)) { + return false; + } + return getSymlinkTarget(systemPath) === repoPath; } async import(systemPath: string, repoPath: string): Promise { @@ -65,6 +69,20 @@ export class AntigravityAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + return { + success: true, + message: "Already linked to repo - no import needed", + }; + } + + if (exists(repoPath)) { + return { + success: true, + message: "Configs already in repo - no import needed", + }; + } + ensureDir(repoPath); copyDir(systemPath, repoPath); @@ -82,6 +100,17 @@ export class AntigravityAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + const target = getSymlinkTarget(systemPath); + if (target === repoPath) { + return { + success: true, + message: "Already linked to repo - no export needed", + linkedTo: repoPath, + }; + } + } + // Remove existing (symlink or directory) if (exists(systemPath)) { if (isSymlink(systemPath)) { diff --git a/src/adapters/claude.ts b/src/adapters/claude.ts index 8ae0a7d..0e32e1e 100644 --- a/src/adapters/claude.ts +++ b/src/adapters/claude.ts @@ -2,14 +2,14 @@ * Claude Code adapter */ -import { copyFileSync, existsSync, statSync, unlinkSync } from "node:fs"; +import { copyFileSync, existsSync, statSync } from "node:fs"; import { dirname, join } from "node:path"; import { copyDir, ensureDir, exists, isDirectory, - removeDir, + isSymlink, } from "../utils/fs"; import { contractHome } from "../utils/paths"; import type { @@ -89,6 +89,10 @@ export class ClaudeAdapter implements AgentAdapter { if (!existsSync(srcPath)) continue; + if (isSymlink(srcPath)) continue; + + if (existsSync(destPath)) continue; + if (statSync(srcPath).isDirectory()) { copyDir(srcPath, destPath); filesImported.push(`${pattern}/`); @@ -134,14 +138,7 @@ export class ClaudeAdapter implements AgentAdapter { if (!existsSync(srcPath)) continue; - // Remove existing and copy fresh - if (existsSync(destPath)) { - if (statSync(destPath).isDirectory()) { - removeDir(destPath); - } else { - unlinkSync(destPath); - } - } + if (existsSync(destPath)) continue; if (statSync(srcPath).isDirectory()) { copyDir(srcPath, destPath); diff --git a/src/adapters/clawdbot.ts b/src/adapters/clawdbot.ts index e1262b4..07ca67f 100644 --- a/src/adapters/clawdbot.ts +++ b/src/adapters/clawdbot.ts @@ -9,6 +9,7 @@ import { createSymlink, ensureDir, exists, + getSymlinkTarget, isSymlink, removeDir, } from "../utils/fs"; @@ -62,7 +63,10 @@ export class ClawdbotAdapter implements AgentAdapter { } isLinked(systemPath: string, repoPath: string): boolean { - return exists(systemPath) && exists(repoPath) && isSymlink(systemPath); + if (!exists(systemPath) || !exists(repoPath) || !isSymlink(systemPath)) { + return false; + } + return getSymlinkTarget(systemPath) === repoPath; } async import(systemPath: string, repoPath: string): Promise { @@ -73,6 +77,20 @@ export class ClawdbotAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + return { + success: true, + message: "Already linked to repo - no import needed", + }; + } + + if (exists(repoPath)) { + return { + success: true, + message: "Configs already in repo - no import needed", + }; + } + ensureDir(repoPath); copyDir(systemPath, repoPath); @@ -110,6 +128,17 @@ export class ClawdbotAdapter implements AgentAdapter { repoPath: string, systemPath: string, ): Promise { + if (isSymlink(systemPath)) { + const target = getSymlinkTarget(systemPath); + if (target === repoPath) { + return { + success: true, + message: "Already linked to repo - no export needed", + linkedTo: repoPath, + }; + } + } + if (exists(systemPath) && !isSymlink(systemPath)) { const backupPath = `${systemPath}.backup`; if (exists(backupPath)) { diff --git a/src/adapters/codex.ts b/src/adapters/codex.ts index 720933d..f9a6e7c 100644 --- a/src/adapters/codex.ts +++ b/src/adapters/codex.ts @@ -9,6 +9,7 @@ import { createSymlink, ensureDir, exists, + getSymlinkTarget, isSymlink, removeDir, } from "../utils/fs"; @@ -52,7 +53,10 @@ export class CodexAdapter implements AgentAdapter { } isLinked(systemPath: string, repoPath: string): boolean { - return exists(systemPath) && exists(repoPath) && isSymlink(systemPath); + if (!exists(systemPath) || !exists(repoPath) || !isSymlink(systemPath)) { + return false; + } + return getSymlinkTarget(systemPath) === repoPath; } async import(systemPath: string, repoPath: string): Promise { @@ -63,6 +67,20 @@ export class CodexAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + return { + success: true, + message: "Already linked to repo - no import needed", + }; + } + + if (exists(repoPath)) { + return { + success: true, + message: "Configs already in repo - no import needed", + }; + } + ensureDir(repoPath); copyDir(systemPath, repoPath); @@ -80,6 +98,17 @@ export class CodexAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + const target = getSymlinkTarget(systemPath); + if (target === repoPath) { + return { + success: true, + message: "Already linked to repo - no export needed", + linkedTo: repoPath, + }; + } + } + // Remove existing (symlink or directory) if (exists(systemPath)) { if (isSymlink(systemPath)) { diff --git a/src/adapters/cursor.ts b/src/adapters/cursor.ts index 22170a7..10b7db9 100644 --- a/src/adapters/cursor.ts +++ b/src/adapters/cursor.ts @@ -116,6 +116,10 @@ export class CursorAdapter implements AgentAdapter { if (!existsSync(srcPath)) continue; + if (isSymlink(srcPath)) continue; + + if (existsSync(destPath)) continue; + if (statSync(srcPath).isDirectory()) { copyDir(srcPath, destPath); filesImported.push(`${pattern}/`); @@ -159,8 +163,13 @@ export class CursorAdapter implements AgentAdapter { if (!existsSync(srcPath)) continue; - // Backup existing file/dir if not a symlink - if (exists(destPath) && !isSymlink(destPath)) { + if (isSymlink(destPath)) { + const target = getSymlinkTarget(destPath); + if (target === srcPath) { + continue; + } + unlinkSync(destPath); + } else if (exists(destPath)) { const backupPath = `${destPath}.backup`; if (exists(backupPath)) { if (isDirectory(backupPath)) { @@ -173,8 +182,6 @@ export class CursorAdapter implements AgentAdapter { if (!backedUp) { backedUp = contractHome(dirname(backupPath)); } - } else if (isSymlink(destPath)) { - unlinkSync(destPath); } // Create symlink diff --git a/src/adapters/dotfiles.ts b/src/adapters/dotfiles.ts index 4d2fb49..90b502d 100644 --- a/src/adapters/dotfiles.ts +++ b/src/adapters/dotfiles.ts @@ -153,6 +153,14 @@ export class DotfilesAdapter implements AgentAdapter { continue; } + if (isSymlink(srcPath)) { + continue; + } + + if (existsSync(destPath)) { + continue; + } + try { if (mapping.isDir) { if (!statSync(srcPath).isDirectory()) { @@ -221,7 +229,13 @@ export class DotfilesAdapter implements AgentAdapter { try { ensureDir(dirname(destPath)); - if (exists(destPath) && !isSymlink(destPath)) { + if (isSymlink(destPath)) { + const target = getSymlinkTarget(destPath); + if (target === srcPath) { + continue; + } + unlinkSync(destPath); + } else if (exists(destPath)) { const backupPath = `${destPath}.backup`; if (exists(backupPath)) { if (isDirectory(backupPath)) { @@ -234,8 +248,6 @@ export class DotfilesAdapter implements AgentAdapter { if (!backedUp) { backedUp = contractHome(dirname(backupPath)); } - } else if (isSymlink(destPath)) { - unlinkSync(destPath); } symlinkSync(srcPath, destPath); diff --git a/src/adapters/droid.ts b/src/adapters/droid.ts index 2e1fbd3..2449ce0 100644 --- a/src/adapters/droid.ts +++ b/src/adapters/droid.ts @@ -9,6 +9,7 @@ import { createSymlink, ensureDir, exists, + getSymlinkTarget, isSymlink, removeDir, } from "../utils/fs"; @@ -52,7 +53,10 @@ export class DroidAdapter implements AgentAdapter { } isLinked(systemPath: string, repoPath: string): boolean { - return exists(systemPath) && exists(repoPath) && isSymlink(systemPath); + if (!exists(systemPath) || !exists(repoPath) || !isSymlink(systemPath)) { + return false; + } + return getSymlinkTarget(systemPath) === repoPath; } async import(systemPath: string, repoPath: string): Promise { @@ -63,6 +67,20 @@ export class DroidAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + return { + success: true, + message: "Already linked to repo - no import needed", + }; + } + + if (exists(repoPath)) { + return { + success: true, + message: "Configs already in repo - no import needed", + }; + } + ensureDir(repoPath); copyDir(systemPath, repoPath); @@ -80,6 +98,17 @@ export class DroidAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + const target = getSymlinkTarget(systemPath); + if (target === repoPath) { + return { + success: true, + message: "Already linked to repo - no export needed", + linkedTo: repoPath, + }; + } + } + // Remove existing (symlink or directory) if (exists(systemPath)) { if (isSymlink(systemPath)) { diff --git a/src/adapters/gemini-cli.ts b/src/adapters/gemini-cli.ts index 189fc3d..7aa5f15 100644 --- a/src/adapters/gemini-cli.ts +++ b/src/adapters/gemini-cli.ts @@ -9,6 +9,7 @@ import { createSymlink, ensureDir, exists, + getSymlinkTarget, isSymlink, removeDir, } from "../utils/fs"; @@ -52,7 +53,10 @@ export class GeminiCliAdapter implements AgentAdapter { } isLinked(systemPath: string, repoPath: string): boolean { - return exists(systemPath) && exists(repoPath) && isSymlink(systemPath); + if (!exists(systemPath) || !exists(repoPath) || !isSymlink(systemPath)) { + return false; + } + return getSymlinkTarget(systemPath) === repoPath; } async import(systemPath: string, repoPath: string): Promise { @@ -63,6 +67,20 @@ export class GeminiCliAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + return { + success: true, + message: "Already linked to repo - no import needed", + }; + } + + if (exists(repoPath)) { + return { + success: true, + message: "Configs already in repo - no import needed", + }; + } + ensureDir(repoPath); copyDir(systemPath, repoPath); @@ -80,6 +98,17 @@ export class GeminiCliAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + const target = getSymlinkTarget(systemPath); + if (target === repoPath) { + return { + success: true, + message: "Already linked to repo - no export needed", + linkedTo: repoPath, + }; + } + } + // Remove existing (symlink or directory) if (exists(systemPath)) { if (isSymlink(systemPath)) { diff --git a/src/adapters/github-copilot.ts b/src/adapters/github-copilot.ts index de9047d..e2d3ccd 100644 --- a/src/adapters/github-copilot.ts +++ b/src/adapters/github-copilot.ts @@ -9,6 +9,7 @@ import { createSymlink, ensureDir, exists, + getSymlinkTarget, isSymlink, removeDir, } from "../utils/fs"; @@ -54,7 +55,10 @@ export class GithubCopilotAdapter implements AgentAdapter { } isLinked(systemPath: string, repoPath: string): boolean { - return exists(systemPath) && exists(repoPath) && isSymlink(systemPath); + if (!exists(systemPath) || !exists(repoPath) || !isSymlink(systemPath)) { + return false; + } + return getSymlinkTarget(systemPath) === repoPath; } async import(systemPath: string, repoPath: string): Promise { @@ -65,6 +69,20 @@ export class GithubCopilotAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + return { + success: true, + message: "Already linked to repo - no import needed", + }; + } + + if (exists(repoPath)) { + return { + success: true, + message: "Configs already in repo - no import needed", + }; + } + ensureDir(repoPath); copyDir(systemPath, repoPath); @@ -82,6 +100,17 @@ export class GithubCopilotAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + const target = getSymlinkTarget(systemPath); + if (target === repoPath) { + return { + success: true, + message: "Already linked to repo - no export needed", + linkedTo: repoPath, + }; + } + } + // Remove existing (symlink or directory) if (exists(systemPath)) { if (isSymlink(systemPath)) { diff --git a/src/adapters/goose.ts b/src/adapters/goose.ts index 5e4979d..1033a9c 100644 --- a/src/adapters/goose.ts +++ b/src/adapters/goose.ts @@ -9,6 +9,7 @@ import { createSymlink, ensureDir, exists, + getSymlinkTarget, isSymlink, removeDir, } from "../utils/fs"; @@ -52,7 +53,10 @@ export class GooseAdapter implements AgentAdapter { } isLinked(systemPath: string, repoPath: string): boolean { - return exists(systemPath) && exists(repoPath) && isSymlink(systemPath); + if (!exists(systemPath) || !exists(repoPath) || !isSymlink(systemPath)) { + return false; + } + return getSymlinkTarget(systemPath) === repoPath; } async import(systemPath: string, repoPath: string): Promise { @@ -63,6 +67,20 @@ export class GooseAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + return { + success: true, + message: "Already linked to repo - no import needed", + }; + } + + if (exists(repoPath)) { + return { + success: true, + message: "Configs already in repo - no import needed", + }; + } + ensureDir(repoPath); copyDir(systemPath, repoPath); @@ -80,6 +98,17 @@ export class GooseAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + const target = getSymlinkTarget(systemPath); + if (target === repoPath) { + return { + success: true, + message: "Already linked to repo - no export needed", + linkedTo: repoPath, + }; + } + } + // Remove existing (symlink or directory) if (exists(systemPath)) { if (isSymlink(systemPath)) { diff --git a/src/adapters/kilo.ts b/src/adapters/kilo.ts index 2f43985..f2fd214 100644 --- a/src/adapters/kilo.ts +++ b/src/adapters/kilo.ts @@ -9,6 +9,7 @@ import { createSymlink, ensureDir, exists, + getSymlinkTarget, isSymlink, removeDir, } from "../utils/fs"; @@ -52,7 +53,10 @@ export class KiloAdapter implements AgentAdapter { } isLinked(systemPath: string, repoPath: string): boolean { - return exists(systemPath) && exists(repoPath) && isSymlink(systemPath); + if (!exists(systemPath) || !exists(repoPath) || !isSymlink(systemPath)) { + return false; + } + return getSymlinkTarget(systemPath) === repoPath; } async import(systemPath: string, repoPath: string): Promise { @@ -63,6 +67,20 @@ export class KiloAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + return { + success: true, + message: "Already linked to repo - no import needed", + }; + } + + if (exists(repoPath)) { + return { + success: true, + message: "Configs already in repo - no import needed", + }; + } + ensureDir(repoPath); copyDir(systemPath, repoPath); @@ -80,6 +98,17 @@ export class KiloAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + const target = getSymlinkTarget(systemPath); + if (target === repoPath) { + return { + success: true, + message: "Already linked to repo - no export needed", + linkedTo: repoPath, + }; + } + } + // Remove existing (symlink or directory) if (exists(systemPath)) { if (isSymlink(systemPath)) { diff --git a/src/adapters/kiro-cli.ts b/src/adapters/kiro-cli.ts index 164c77f..36b19a0 100644 --- a/src/adapters/kiro-cli.ts +++ b/src/adapters/kiro-cli.ts @@ -9,6 +9,7 @@ import { createSymlink, ensureDir, exists, + getSymlinkTarget, isSymlink, removeDir, } from "../utils/fs"; @@ -52,7 +53,10 @@ export class KiroCliAdapter implements AgentAdapter { } isLinked(systemPath: string, repoPath: string): boolean { - return exists(systemPath) && exists(repoPath) && isSymlink(systemPath); + if (!exists(systemPath) || !exists(repoPath) || !isSymlink(systemPath)) { + return false; + } + return getSymlinkTarget(systemPath) === repoPath; } async import(systemPath: string, repoPath: string): Promise { @@ -63,6 +67,20 @@ export class KiroCliAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + return { + success: true, + message: "Already linked to repo - no import needed", + }; + } + + if (exists(repoPath)) { + return { + success: true, + message: "Configs already in repo - no import needed", + }; + } + ensureDir(repoPath); copyDir(systemPath, repoPath); @@ -80,6 +98,17 @@ export class KiroCliAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + const target = getSymlinkTarget(systemPath); + if (target === repoPath) { + return { + success: true, + message: "Already linked to repo - no export needed", + linkedTo: repoPath, + }; + } + } + // Remove existing (symlink or directory) if (exists(systemPath)) { if (isSymlink(systemPath)) { diff --git a/src/adapters/opencode.ts b/src/adapters/opencode.ts index 45623c0..cce2b6f 100644 --- a/src/adapters/opencode.ts +++ b/src/adapters/opencode.ts @@ -96,6 +96,13 @@ export class OpenCodeAdapter implements AgentAdapter { }; } + if (exists(repoPath)) { + return { + success: true, + message: "Configs already in repo - no import needed", + }; + } + ensureDir(repoPath); const filesImported: string[] = []; @@ -131,6 +138,17 @@ export class OpenCodeAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + const target = getSymlinkTarget(systemPath); + if (target === repoPath) { + return { + success: true, + message: "Already linked to repo - no export needed", + linkedTo: contractHome(repoPath), + }; + } + } + // Backup existing directory if it exists and isn't a symlink let backedUp: string | undefined; if (exists(systemPath) && !isSymlink(systemPath)) { diff --git a/src/adapters/roo.ts b/src/adapters/roo.ts index 0817485..3069ae9 100644 --- a/src/adapters/roo.ts +++ b/src/adapters/roo.ts @@ -9,6 +9,7 @@ import { createSymlink, ensureDir, exists, + getSymlinkTarget, isSymlink, removeDir, } from "../utils/fs"; @@ -52,7 +53,10 @@ export class RooAdapter implements AgentAdapter { } isLinked(systemPath: string, repoPath: string): boolean { - return exists(systemPath) && exists(repoPath) && isSymlink(systemPath); + if (!exists(systemPath) || !exists(repoPath) || !isSymlink(systemPath)) { + return false; + } + return getSymlinkTarget(systemPath) === repoPath; } async import(systemPath: string, repoPath: string): Promise { @@ -63,6 +67,20 @@ export class RooAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + return { + success: true, + message: "Already linked to repo - no import needed", + }; + } + + if (exists(repoPath)) { + return { + success: true, + message: "Configs already in repo - no import needed", + }; + } + ensureDir(repoPath); copyDir(systemPath, repoPath); @@ -80,6 +98,17 @@ export class RooAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + const target = getSymlinkTarget(systemPath); + if (target === repoPath) { + return { + success: true, + message: "Already linked to repo - no export needed", + linkedTo: repoPath, + }; + } + } + // Remove existing (symlink or directory) if (exists(systemPath)) { if (isSymlink(systemPath)) { diff --git a/src/adapters/trae.ts b/src/adapters/trae.ts index 1933839..d9c4061 100644 --- a/src/adapters/trae.ts +++ b/src/adapters/trae.ts @@ -9,6 +9,7 @@ import { createSymlink, ensureDir, exists, + getSymlinkTarget, isSymlink, removeDir, } from "../utils/fs"; @@ -52,7 +53,10 @@ export class TraeAdapter implements AgentAdapter { } isLinked(systemPath: string, repoPath: string): boolean { - return exists(systemPath) && exists(repoPath) && isSymlink(systemPath); + if (!exists(systemPath) || !exists(repoPath) || !isSymlink(systemPath)) { + return false; + } + return getSymlinkTarget(systemPath) === repoPath; } async import(systemPath: string, repoPath: string): Promise { @@ -63,6 +67,20 @@ export class TraeAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + return { + success: true, + message: "Already linked to repo - no import needed", + }; + } + + if (exists(repoPath)) { + return { + success: true, + message: "Configs already in repo - no import needed", + }; + } + ensureDir(repoPath); copyDir(systemPath, repoPath); @@ -80,6 +98,17 @@ export class TraeAdapter implements AgentAdapter { }; } + if (isSymlink(systemPath)) { + const target = getSymlinkTarget(systemPath); + if (target === repoPath) { + return { + success: true, + message: "Already linked to repo - no export needed", + linkedTo: repoPath, + }; + } + } + // Remove existing (symlink or directory) if (exists(systemPath)) { if (isSymlink(systemPath)) { diff --git a/src/adapters/vscode.ts b/src/adapters/vscode.ts index 4982e62..216efb6 100644 --- a/src/adapters/vscode.ts +++ b/src/adapters/vscode.ts @@ -117,6 +117,10 @@ export class VSCodeAdapter implements AgentAdapter { if (!existsSync(srcPath)) continue; + if (isSymlink(srcPath)) continue; + + if (existsSync(destPath)) continue; + if (statSync(srcPath).isDirectory()) { copyDir(srcPath, destPath); filesImported.push(`${pattern}/`); @@ -160,8 +164,13 @@ export class VSCodeAdapter implements AgentAdapter { if (!existsSync(srcPath)) continue; - // Backup existing file/dir if not a symlink - if (exists(destPath) && !isSymlink(destPath)) { + if (isSymlink(destPath)) { + const target = getSymlinkTarget(destPath); + if (target === srcPath) { + continue; + } + unlinkSync(destPath); + } else if (exists(destPath)) { const backupPath = `${destPath}.backup`; if (exists(backupPath)) { if (isDirectory(backupPath)) { @@ -174,8 +183,6 @@ export class VSCodeAdapter implements AgentAdapter { if (!backedUp) { backedUp = contractHome(dirname(backupPath)); } - } else if (isSymlink(destPath)) { - unlinkSync(destPath); } // Create symlink diff --git a/src/adapters/windsurf.ts b/src/adapters/windsurf.ts index 91a8d8d..9d5d46a 100644 --- a/src/adapters/windsurf.ts +++ b/src/adapters/windsurf.ts @@ -105,6 +105,10 @@ export class WindsurfAdapter implements AgentAdapter { if (!existsSync(srcPath)) continue; + if (isSymlink(srcPath)) continue; + + if (existsSync(destPath)) continue; + if (statSync(srcPath).isDirectory()) { copyDir(srcPath, destPath); filesImported.push(`${pattern}/`); @@ -148,8 +152,13 @@ export class WindsurfAdapter implements AgentAdapter { if (!existsSync(srcPath)) continue; - // Backup existing file/dir if not a symlink - if (exists(destPath) && !isSymlink(destPath)) { + if (isSymlink(destPath)) { + const target = getSymlinkTarget(destPath); + if (target === srcPath) { + continue; + } + unlinkSync(destPath); + } else if (exists(destPath)) { const backupPath = `${destPath}.backup`; if (exists(backupPath)) { if (isDirectory(backupPath)) { @@ -162,8 +171,6 @@ export class WindsurfAdapter implements AgentAdapter { if (!backedUp) { backedUp = contractHome(dirname(backupPath)); } - } else if (isSymlink(destPath)) { - unlinkSync(destPath); } // Create symlink From 541e6889a503d67cc484256d125c6a5f2169447c Mon Sep 17 00:00:00 2001 From: Donald Silveia Date: Mon, 2 Feb 2026 20:18:44 -0300 Subject: [PATCH 2/3] Missing --- scripts/release.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/release.ts b/scripts/release.ts index c476701..1f42c32 100644 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -52,6 +52,8 @@ pkg.version = newVersion; await writeFile("package.json", `${JSON.stringify(pkg, null, 2)}\n`); console.log("Updated package.json"); +await $`bun check:unsafe`; + // 5. Git operations const tag = `v${newVersion}`; let output = `version=${newVersion}\ntag=${tag}\n`; From 2c2d34e110b81f50c91239ac9c44d7caeb7ba8d8 Mon Sep 17 00:00:00 2001 From: Donald Silveia Date: Mon, 2 Feb 2026 20:20:28 -0300 Subject: [PATCH 3/3] Format --- package.json | 132 +++++++++++++++++++++++++-------------------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/package.json b/package.json index acffb1e..2bb4490 100644 --- a/package.json +++ b/package.json @@ -1,68 +1,68 @@ { - "name": "@donnes/syncode", - "version": "1.1.7", - "description": "Sync AI code agent configs (Claude Code, Cursor, Windsurf, OpenCode) across machines and projects.", - "private": false, - "type": "module", - "bin": { - "syncode": "./dist/index.js" - }, - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "files": [ - "dist", - "README.md", - "LICENSE" - ], - "scripts": { - "build": "tsup", - "dev": "tsx src/index.ts", - "test": "tsx scripts/execute-tests.ts", - "prepublishOnly": "npm run build", - "check:lint": "biome check . --diagnostic-level=error", - "check:unsafe": "biome check . --write --unsafe --diagnostic-level=error", - "check:types": "bunx tsc --noEmit" - }, - "keywords": [ - "ai-agents", - "claude-code", - "cursor", - "windsurf", - "opencode", - "vscode", - "ai-coding", - "code-agent", - "skill-conversion", - "syncode", - "dotfiles", - "configuration-management", - "developer-tools", - "ai-assistant", - "coding-assistant" - ], - "engines": { - "node": ">=20.0.0" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/donnes/syncode.git" - }, - "bugs": { - "url": "https://github.com/donnes/syncode/issues" - }, - "homepage": "https://github.com/donnes/syncode#readme", - "author": "Donald Silveira", - "license": "MIT", - "dependencies": { - "@clack/prompts": "^0.9.1" - }, - "devDependencies": { - "@biomejs/biome": "^2.3.11", - "@opencode-ai/sdk": "^1.1.41", - "@types/bun": "latest", - "@types/node": "^20.0.0", - "tsup": "^8.5.1", - "tsx": "^4.21.0", - "typescript": "^5.7.3" - } + "name": "@donnes/syncode", + "version": "1.1.7", + "description": "Sync AI code agent configs (Claude Code, Cursor, Windsurf, OpenCode) across machines and projects.", + "private": false, + "type": "module", + "bin": { + "syncode": "./dist/index.js" + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist", + "README.md", + "LICENSE" + ], + "scripts": { + "build": "tsup", + "dev": "tsx src/index.ts", + "test": "tsx scripts/execute-tests.ts", + "prepublishOnly": "npm run build", + "check:lint": "biome check . --diagnostic-level=error", + "check:unsafe": "biome check . --write --unsafe --diagnostic-level=error", + "check:types": "bunx tsc --noEmit" + }, + "keywords": [ + "ai-agents", + "claude-code", + "cursor", + "windsurf", + "opencode", + "vscode", + "ai-coding", + "code-agent", + "skill-conversion", + "syncode", + "dotfiles", + "configuration-management", + "developer-tools", + "ai-assistant", + "coding-assistant" + ], + "engines": { + "node": ">=20.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/donnes/syncode.git" + }, + "bugs": { + "url": "https://github.com/donnes/syncode/issues" + }, + "homepage": "https://github.com/donnes/syncode#readme", + "author": "Donald Silveira", + "license": "MIT", + "dependencies": { + "@clack/prompts": "^0.9.1" + }, + "devDependencies": { + "@biomejs/biome": "^2.3.11", + "@opencode-ai/sdk": "^1.1.41", + "@types/bun": "latest", + "@types/node": "^20.0.0", + "tsup": "^8.5.1", + "tsx": "^4.21.0", + "typescript": "^5.7.3" + } }