Skip to content

Commit 9bbfa6c

Browse files
committed
feat: complete sds clean --global for all platforms
Add full cleanup coverage for GitHub Copilot CLI (agents, commands, skills), Qwen Code (agent/command files in skills/ dir), and restructured clean preview to show specific filenames for all 5 platforms. Add E2E tests for Copilot CLI, Qwen Code, and VS Code clean operations. Version bump to 0.11.2.
1 parent 669a427 commit 9bbfa6c

3 files changed

Lines changed: 84 additions & 15 deletions

File tree

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "spec-driven-steroids",
3-
"version": "0.11.1",
3+
"version": "0.11.2",
44
"description": "Inject Spec-Driven workflow into your favorite AI Agents. Rigorous. Simple. Frictionless.",
55
"type": "module",
66
"main": "dist/cli/index.js",

packages/cli/src/cli/index.ts

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
} from './github-copilot-scope.js';
2020
import {
2121
GitHubCopilotCliInjectionScope,
22-
getGitHubCopilotCliUserSkillsDir,
2322
getGitHubCopilotCliGlobalConfigDir
2423
} from './github-copilot-cli-scope.js';
2524
import {
@@ -28,8 +27,7 @@ import {
2827
getGeminiCliAgentsAliasDir
2928
} from './gemini-cli-scope.js';
3029
import {
31-
QwenCodeInjectionScope,
32-
getQwenCodeUserSkillsDir
30+
QwenCodeInjectionScope
3331
} from './qwen-code-scope.js';
3432
import { transformTemplates } from './transformation-pipeline.js';
3533
import { createValidateCommand } from '../core/validate/index.js';
@@ -465,26 +463,33 @@ function buildCleanPreview(): string {
465463
const vscodePromptsDir = getVSCodeGlobalPromptsDir();
466464
const vscodeSkillsDir = getCopilotGlobalSkillsDir();
467465
const opencodeDir = getOpenCodeGlobalConfigDir();
468-
const githubCopilotCliSkillsDir = getGitHubCopilotCliUserSkillsDir();
469-
const geminiCliSkillsDir = getGeminiCliUserSkillsDir();
470-
const qwenCodeSkillsDir = getQwenCodeUserSkillsDir();
466+
const copilotCliDir = getGitHubCopilotCliGlobalConfigDir();
467+
const geminiDir = path.join(os.homedir(), '.gemini');
468+
const qwenDir = path.join(os.homedir(), '.qwen');
471469

472470
const formatDir = (d: string) => d.replace(os.homedir(), '~');
473471

474472
const lines = [
475473
chalk.bold.cyan('🔍 Preview of --global clean:\n'),
476474
` ${chalk.bold('GitHub Copilot for VS Code')}:`,
477-
` • Prompts: ${formatDir(vscodePromptsDir)}/`,
478-
` • Skills: ${formatDir(vscodeSkillsDir)}/`,
475+
` • ${formatDir(vscodePromptsDir)}/spec-driven.agent.md`,
476+
` • ${formatDir(vscodePromptsDir)}/spec-driven.prompt.md`,
477+
` • skills/ under ${formatDir(vscodeSkillsDir)}/`,
479478
'',
480-
` ${chalk.bold('GitHub Copilot CLI')}:`,
481-
` • Skills: ${formatDir(githubCopilotCliSkillsDir)}/`,
479+
` ${chalk.bold('GitHub Copilot CLI')} (${formatDir(copilotCliDir)}):`,
480+
` • agents/spec-driven.agent.md`,
481+
` • commands/spec-driven.md, commands/inject-guidelines.md`,
482+
` • skills/`,
482483
'',
483-
` ${chalk.bold('Gemini CLI')}:`,
484-
` • Skills: ${formatDir(geminiCliSkillsDir)}/`,
484+
` ${chalk.bold('Gemini CLI')} (${formatDir(geminiDir)}):`,
485+
` • agents/spec-driven.md`,
486+
` • commands/spec-driven.toml, commands/inject-guidelines.toml`,
487+
` • commands/spec-driven.md, commands/inject-guidelines.md`,
488+
` • skills/`,
485489
'',
486-
` ${chalk.bold('Qwen Code')}:`,
487-
` • Skills: ${formatDir(qwenCodeSkillsDir)}/`,
490+
` ${chalk.bold('Qwen Code')} (${formatDir(qwenDir)}):`,
491+
` • skills/spec-driven.md, skills/inject-guidelines.md`,
492+
` • skills/${STEROIDS_SKILL_DIRS.join('/, skills/')}/`,
488493
'',
489494
` ${chalk.bold('OpenCode')} (${formatDir(opencodeDir)}):`,
490495
` • agents/${STEROIDS_FILES.agents.join(', ')}`,
@@ -576,6 +581,7 @@ async function removeOpenCodeGlobalSteroids(): Promise<boolean> {
576581

577582
async function removeGitHubCopilotCliSteroids(): Promise<boolean> {
578583
try {
584+
await removeSteroidsFiles(getGitHubCopilotCliGlobalConfigDir());
579585
await removeSteroidsSkillDirs(getGitHubCopilotCliGlobalConfigDir());
580586
console.log(chalk.green(' ✅ GitHub Copilot CLI cleaned.'));
581587
return true;
@@ -630,8 +636,23 @@ async function removeGeminiCliGlobalFiles(): Promise<void> {
630636
}
631637
}
632638

639+
async function removeQwenCodeGlobalFiles(): Promise<void> {
640+
const qwenDir = path.join(os.homedir(), '.qwen');
641+
const skillsDir = path.join(qwenDir, 'skills');
642+
const filesToRemove = [
643+
path.join(skillsDir, 'spec-driven.md'),
644+
path.join(skillsDir, 'inject-guidelines.md')
645+
];
646+
for (const file of filesToRemove) {
647+
if (await fs.pathExists(file)) {
648+
await fs.remove(file);
649+
}
650+
}
651+
}
652+
633653
async function removeQwenCodeSteroids(): Promise<boolean> {
634654
try {
655+
await removeQwenCodeGlobalFiles();
635656
await removeSteroidsSkillDirs(path.join(os.homedir(), '.qwen'));
636657
console.log(chalk.green(' ✅ Qwen Code cleaned.'));
637658
return true;

packages/cli/tests/integration/clean-global.e2e.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,54 @@ describe('CLI E2E: clean --global command', () => {
6262
expect(await fs.pathExists(path.join(geminiDir, 'skills', 'long-running-work-planning'))).toBe(false);
6363
});
6464

65+
it('clean --global removes GitHub Copilot CLI global artifacts', async () => {
66+
const copilotCliDir = path.join(os.homedir(), '.config', 'github-copilot');
67+
68+
await fs.outputFile(path.join(copilotCliDir, 'agents', 'spec-driven.agent.md'), 'agent');
69+
await fs.outputFile(path.join(copilotCliDir, 'commands', 'spec-driven.md'), 'command');
70+
await fs.outputFile(path.join(copilotCliDir, 'commands', 'inject-guidelines.md'), 'command');
71+
await fs.outputFile(path.join(copilotCliDir, 'skills', 'long-running-work-planning', 'SKILL.md'), 'skill');
72+
73+
const program = (await import('../../dist/cli/index.js')).default;
74+
await program.parseAsync(['clean', '--global', '--yes'], { from: 'user' } as any);
75+
76+
expect(await fs.pathExists(path.join(copilotCliDir, 'agents', 'spec-driven.agent.md'))).toBe(false);
77+
expect(await fs.pathExists(path.join(copilotCliDir, 'commands', 'spec-driven.md'))).toBe(false);
78+
expect(await fs.pathExists(path.join(copilotCliDir, 'commands', 'inject-guidelines.md'))).toBe(false);
79+
expect(await fs.pathExists(path.join(copilotCliDir, 'skills', 'long-running-work-planning'))).toBe(false);
80+
});
81+
82+
it('clean --global removes Qwen Code global artifacts', async () => {
83+
const qwenDir = path.join(os.homedir(), '.qwen');
84+
85+
await fs.outputFile(path.join(qwenDir, 'skills', 'spec-driven.md'), 'agent');
86+
await fs.outputFile(path.join(qwenDir, 'skills', 'inject-guidelines.md'), 'command');
87+
await fs.outputFile(path.join(qwenDir, 'skills', 'long-running-work-planning', 'SKILL.md'), 'skill');
88+
89+
const program = (await import('../../dist/cli/index.js')).default;
90+
await program.parseAsync(['clean', '--global', '--yes'], { from: 'user' } as any);
91+
92+
expect(await fs.pathExists(path.join(qwenDir, 'skills', 'spec-driven.md'))).toBe(false);
93+
expect(await fs.pathExists(path.join(qwenDir, 'skills', 'inject-guidelines.md'))).toBe(false);
94+
expect(await fs.pathExists(path.join(qwenDir, 'skills', 'long-running-work-planning'))).toBe(false);
95+
});
96+
97+
it('clean --global removes GitHub Copilot for VS Code global artifacts', async () => {
98+
const promptsDir = path.join(os.homedir(), 'Library', 'Application Support', 'Code', 'User', 'prompts');
99+
const skillsDir = path.join(os.homedir(), '.copilot', 'skills');
100+
101+
await fs.outputFile(path.join(promptsDir, 'spec-driven.agent.md'), 'agent');
102+
await fs.outputFile(path.join(promptsDir, 'spec-driven.prompt.md'), 'prompt');
103+
await fs.outputFile(path.join(skillsDir, 'long-running-work-planning', 'SKILL.md'), 'skill');
104+
105+
const program = (await import('../../dist/cli/index.js')).default;
106+
await program.parseAsync(['clean', '--global', '--yes'], { from: 'user' } as any);
107+
108+
expect(await fs.pathExists(path.join(promptsDir, 'spec-driven.agent.md'))).toBe(false);
109+
expect(await fs.pathExists(path.join(promptsDir, 'spec-driven.prompt.md'))).toBe(false);
110+
expect(await fs.pathExists(path.join(skillsDir, 'long-running-work-planning'))).toBe(false);
111+
});
112+
65113
it('clean --global handles missing directories gracefully', async () => {
66114
const program = (await import('../../dist/cli/index.js')).default;
67115

0 commit comments

Comments
 (0)