diff --git a/tests/copilot-install.test.cjs b/tests/copilot-install.test.cjs index 25ec66c1e..c80ddff53 100644 --- a/tests/copilot-install.test.cjs +++ b/tests/copilot-install.test.cjs @@ -1341,3 +1341,87 @@ describe('E2E: Copilot uninstall verification', () => { }); }); }); + +// ─── Claude uninstall: user file preservation (#1423) ───────────────────────── + +function runClaudeInstall(cwd) { + const env = { ...process.env }; + delete env.GSD_TEST_MODE; + return execFileSync(process.execPath, [INSTALL_PATH, '--claude', '--local'], { + cwd, + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + env, + }); +} + +function runClaudeUninstall(cwd) { + const env = { ...process.env }; + delete env.GSD_TEST_MODE; + return execFileSync(process.execPath, [INSTALL_PATH, '--claude', '--local', '--uninstall'], { + cwd, + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + env, + }); +} + +describe('Claude uninstall preserves user-generated files (#1423)', () => { + let tmpDir; + + beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-preserve-')); + runClaudeInstall(tmpDir); + }); + + afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }); + }); + + test('preserves USER-PROFILE.md across uninstall', () => { + const profilePath = path.join(tmpDir, '.claude', 'get-shit-done', 'USER-PROFILE.md'); + const content = '# Developer Profile\n\nAutonomy: High\nGenerated: 2026-03-29\n'; + fs.writeFileSync(profilePath, content); + + runClaudeUninstall(tmpDir); + + assert.ok(fs.existsSync(profilePath), 'USER-PROFILE.md should survive uninstall'); + assert.strictEqual(fs.readFileSync(profilePath, 'utf-8'), content, 'content should be identical'); + }); + + test('preserves dev-preferences.md across uninstall', () => { + const prefsPath = path.join(tmpDir, '.claude', 'commands', 'gsd', 'dev-preferences.md'); + const content = '---\nname: dev-preferences\n---\n# Preferences\nUse TypeScript strict.\n'; + fs.writeFileSync(prefsPath, content); + + runClaudeUninstall(tmpDir); + + assert.ok(fs.existsSync(prefsPath), 'dev-preferences.md should survive uninstall'); + assert.strictEqual(fs.readFileSync(prefsPath, 'utf-8'), content, 'content should be identical'); + }); + + test('still removes GSD engine files during uninstall', () => { + const profilePath = path.join(tmpDir, '.claude', 'get-shit-done', 'USER-PROFILE.md'); + fs.writeFileSync(profilePath, '# Profile\n'); + + // Verify engine files exist before uninstall + const binDir = path.join(tmpDir, '.claude', 'get-shit-done', 'bin'); + assert.ok(fs.existsSync(binDir), 'bin/ should exist before uninstall'); + + runClaudeUninstall(tmpDir); + + // Engine files gone, user file preserved + assert.ok(!fs.existsSync(binDir), 'bin/ should be removed after uninstall'); + assert.ok(fs.existsSync(profilePath), 'USER-PROFILE.md should survive'); + }); + + test('clean uninstall when no user files exist', () => { + runClaudeUninstall(tmpDir); + + const gsdDir = path.join(tmpDir, '.claude', 'get-shit-done'); + const cmdDir = path.join(tmpDir, '.claude', 'commands', 'gsd'); + // Directories should be fully removed when no user files to preserve + assert.ok(!fs.existsSync(gsdDir), 'get-shit-done/ should not exist after clean uninstall'); + assert.ok(!fs.existsSync(cmdDir), 'commands/gsd/ should not exist after clean uninstall'); + }); +});