import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { mkdir, rm, writeFile, lstat, symlink } from 'node:fs/promises';
import { join, resolve } from 'node:path';
import { tmpdir } from 'node:os';
import { removeCommand } from '../src/remove.ts';
import * as agentsModule from '../src/agents.ts';
vi.mock('../src/agents.ts', async () => {
const actual = await vi.importActual('../src/agents.ts');
return {
...actual,
detectInstalledAgents: vi.fn(),
};
});
describe('removeCommand canonical protection', () => {
let tempDir: string;
let oldCwd: string;
beforeEach(async () => {
tempDir = await resolve(join(tmpdir(), 'skills-remove-test-' + Date.now()));
await mkdir(tempDir, { recursive: true });
oldCwd = process.cwd();
process.chdir(tempDir);
await mkdir(join(tempDir, '.agents/skills'), { recursive: true });
await mkdir(join(tempDir, '.claude/skills'), { recursive: true });
await mkdir(join(tempDir, '.continue/skills'), { recursive: true });
});
afterEach(async () => {
process.chdir(oldCwd);
await rm(tempDir, { recursive: true, force: true });
});
it('should NOT remove canonical storage if other agents still have the skill installed', async () => {
const skillName = 'test-skill';
const canonicalPath = join(tempDir, '.agents/skills', skillName);
const claudePath = join(tempDir, '.claude/skills', skillName);
const continuePath = join(tempDir, '.continue/skills', skillName);
await mkdir(canonicalPath, { recursive: true });
await writeFile(join(canonicalPath, 'SKILL.md'), '# Test');
await symlink(canonicalPath, claudePath, 'junction');
await symlink(canonicalPath, continuePath, 'junction');
expect(
(await lstat(claudePath)).isSymbolicLink() || (await lstat(claudePath)).isDirectory()
).toBe(true);
expect(
(await lstat(continuePath)).isSymbolicLink() || (await lstat(continuePath)).isDirectory()
).toBe(true);
vi.mocked(agentsModule.detectInstalledAgents).mockResolvedValue(['claude-code', 'continue']);
await removeCommand([skillName], { agent: ['claude-code'], yes: true });
await expect(lstat(claudePath)).rejects.toThrow();
expect((await lstat(canonicalPath)).isDirectory()).toBe(true);
expect(
(await lstat(continuePath)).isSymbolicLink() || (await lstat(continuePath)).isDirectory()
).toBe(true);
});
it('should remove canonical storage if NO other agents are using it', async () => {
const skillName = 'test-skill-2';
const canonicalPath = join(tempDir, '.agents/skills', skillName);
const claudePath = join(tempDir, '.claude/skills', skillName);
await mkdir(canonicalPath, { recursive: true });
await writeFile(join(canonicalPath, 'SKILL.md'), '# Test');
await symlink(canonicalPath, claudePath, 'junction');
vi.mocked(agentsModule.detectInstalledAgents).mockResolvedValue(['claude-code']);
await removeCommand([skillName], { agent: ['claude-code'], yes: true });
await expect(lstat(claudePath)).rejects.toThrow();
await expect(lstat(canonicalPath)).rejects.toThrow();
});
});