From 6eed4413efb52ebef851966b5f65b2f44c09f194 Mon Sep 17 00:00:00 2001 From: Ethan Hurst Date: Tue, 17 Mar 2026 11:49:44 +1000 Subject: [PATCH] fix: always sync bundled resources and clean stale files (#761) Remove the version-match early return in initResources() that skipped resource sync when versions matched. This allowed the runtime at ~/.gsd/agent/extensions/ to drift from the bundled resources when individual files were manually copied or leftover from a newer version. Also adds rmSync of bundled subdirectories before each cpSync to remove stale files that exist only in the runtime. User-created extension directories are preserved. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/resource-loader.ts | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/resource-loader.ts b/src/resource-loader.ts index ce5b68de3..c5e02c29b 100644 --- a/src/resource-loader.ts +++ b/src/resource-loader.ts @@ -1,6 +1,6 @@ import { DefaultResourceLoader } from '@gsd/pi-coding-agent' import { homedir } from 'node:os' -import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs' +import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs' import { dirname, join, relative, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { compareSemver } from './update-check.js' @@ -151,28 +151,41 @@ export function getNewerManagedResourceVersion(agentDir: string, currentVersion: export function initResources(agentDir: string): void { mkdirSync(agentDir, { recursive: true }) - // Skip resource sync when versions match — saves ~128ms of cpSync per launch - const currentVersion = getBundledGsdVersion() - const managedVersion = readManagedResourceVersion(agentDir) - if (managedVersion && managedVersion === currentVersion) { - return - } - - // Sync extensions — overwrite so updates land on next launch + // 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 — overwrite so updates land on next launch + // 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 }) }