From 8382e5bfcc9d7842a565eb203eb78927e8b6826f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=82CHES?= Date: Tue, 17 Mar 2026 18:37:59 -0600 Subject: [PATCH] refactor(resource-loader): extract syncResourceDir to eliminate triplicated sync logic (#1036) Co-authored-by: Claude Opus 4.6 (1M context) --- src/resource-loader.ts | 67 ++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 41 deletions(-) diff --git a/src/resource-loader.ts b/src/resource-loader.ts index 65dcdebc2..f40c51015 100644 --- a/src/resource-loader.ts +++ b/src/resource-loader.ts @@ -105,6 +105,29 @@ function makeTreeWritable(dirPath: string): void { } } +/** + * Syncs a single bundled resource directory into the agent directory. + * + * 1. Makes the destination writable (handles Nix store read-only copies). + * 2. Removes destination subdirs that exist in source to clear stale files, + * while preserving user-created directories. + * 3. Copies source into destination. + * 4. Makes the result writable for the next upgrade cycle. + */ +function syncResourceDir(srcDir: string, destDir: string): void { + makeTreeWritable(destDir) + if (existsSync(srcDir)) { + for (const entry of readdirSync(srcDir, { withFileTypes: true })) { + if (entry.isDirectory()) { + const target = join(destDir, entry.name) + if (existsSync(target)) rmSync(target, { recursive: true, force: true }) + } + } + cpSync(srcDir, destDir, { recursive: true, force: true }) + makeTreeWritable(destDir) + } +} + /** * Syncs all bundled resources to agentDir (~/.gsd/agent/) on every launch. * @@ -123,47 +146,9 @@ function makeTreeWritable(dirPath: string): void { export function initResources(agentDir: string): void { mkdirSync(agentDir, { recursive: true }) - // Unlock all existing destination files in a single recursive walk so that - // rmSync and cpSync can overwrite read-only copies from the Nix store. - makeTreeWritable(agentDir) - - // Sync extensions — clean bundled subdirs first to remove stale leftover files, - // then overwrite so updates land on next launch. Only bundled subdirs are removed; - // user-created extension directories are preserved. - const destExtensions = join(agentDir, 'extensions') - for (const entry of readdirSync(bundledExtensionsDir, { withFileTypes: true })) { - if (entry.isDirectory()) { - const target = join(destExtensions, entry.name) - if (existsSync(target)) rmSync(target, { recursive: true, force: true }) - } - } - cpSync(bundledExtensionsDir, destExtensions, { recursive: true, force: true }) - - // Sync agents - const destAgents = join(agentDir, 'agents') - const srcAgents = join(resourcesDir, 'agents') - if (existsSync(srcAgents)) { - for (const entry of readdirSync(srcAgents, { withFileTypes: true })) { - if (entry.isDirectory()) { - const target = join(destAgents, entry.name) - if (existsSync(target)) rmSync(target, { recursive: true, force: true }) - } - } - cpSync(srcAgents, destAgents, { recursive: true, force: true }) - } - - // Sync skills - const destSkills = join(agentDir, 'skills') - const srcSkills = join(resourcesDir, 'skills') - if (existsSync(srcSkills)) { - for (const entry of readdirSync(srcSkills, { withFileTypes: true })) { - if (entry.isDirectory()) { - const target = join(destSkills, entry.name) - if (existsSync(target)) rmSync(target, { recursive: true, force: true }) - } - } - cpSync(srcSkills, destSkills, { recursive: true, force: true }) - } + syncResourceDir(bundledExtensionsDir, join(agentDir, 'extensions')) + syncResourceDir(join(resourcesDir, 'agents'), join(agentDir, 'agents')) + syncResourceDir(join(resourcesDir, 'skills'), join(agentDir, 'skills')) // Ensure all newly copied files are owner-writable so the next run can // overwrite them (covers extensions, agents, and skills in one walk).