Skip to content

Commit 6f994ae

Browse files
committed
test(sync): cover syncing into .agents/skills
1 parent 815cf15 commit 6f994ae

1 file changed

Lines changed: 62 additions & 3 deletions

File tree

‎src/commands/sync.test.ts‎

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { lstat, readlink } from 'node:fs/promises';
1+
import { lstat, readlink, realpath } from 'node:fs/promises';
22
import { fileURLToPath } from 'node:url';
3-
import { describe, it, expect } from 'vitest';
3+
import { describe, it, expect, vi } from 'vitest';
44
import { createFixture, createMocks } from '../test-utils/index.ts';
5-
import { copySkills, findParentPackage } from './sync.ts';
5+
import { copySkills, findParentPackage, sync } from './sync.ts';
66

77
describe('copySkills', () => {
88
it('symlinks each skill into the destination', async () => {
@@ -79,3 +79,62 @@ describe('findParentPackage', () => {
7979
expect(await findParentPackage()).toBe(null);
8080
});
8181
});
82+
83+
describe('sync', () => {
84+
it('links skills into .agents/skills and replaces links from older syncs', async () => {
85+
const fixture = await createFixture({
86+
project: {
87+
'package.json': '{ "name": "my-app" }',
88+
'AGENTS.md': '# Project\n',
89+
'.gitignore': 'node_modules\n\n# bsh:skills\nskills/lifecycle/\n# /bsh:skills\n',
90+
// Left behind by an older version, now dangling.
91+
skills: {
92+
lifecycle: ({ symlink }) =>
93+
symlink('../node_modules/.pnpm/old-hash/node_modules/@bomb.sh/tools/skills/lifecycle'),
94+
'mine.md': 'user-owned',
95+
},
96+
},
97+
});
98+
// Resolve the real path so relative links survive a symlinked tmpdir (macOS).
99+
const project = await realpath(fileURLToPath(new URL('project/', fixture.root)));
100+
createMocks({ env: { INIT_CWD: project } });
101+
vi.spyOn(console, 'info').mockImplementation(() => {});
102+
103+
await sync({ args: [] });
104+
105+
const link = fileURLToPath(new URL('project/.agents/skills/lifecycle', fixture.root));
106+
expect((await lstat(link)).isSymbolicLink()).toBe(true);
107+
expect(await fixture.text('project/.agents/skills/lifecycle/SKILL.md')).toContain(
108+
'name: lifecycle',
109+
);
110+
111+
await expect(
112+
lstat(fileURLToPath(new URL('project/skills/lifecycle', fixture.root))),
113+
).rejects.toThrow();
114+
expect(await fixture.text('project/skills/mine.md')).toBe('user-owned');
115+
116+
const gitignore = await fixture.text('project/.gitignore');
117+
expect(gitignore).toContain('.agents/skills/lifecycle/');
118+
expect(gitignore).not.toMatch(/^skills\//m);
119+
120+
expect(await fixture.text('project/AGENTS.md')).toContain(
121+
'[.agents/skills/lifecycle/SKILL.md](.agents/skills/lifecycle/SKILL.md)',
122+
);
123+
});
124+
125+
it('removes the legacy skills directory once it is empty', async () => {
126+
const fixture = await createFixture({
127+
project: {
128+
'package.json': '{ "name": "my-app" }',
129+
skills: { lifecycle: ({ symlink }) => symlink('../gone/lifecycle') },
130+
},
131+
});
132+
const project = await realpath(fileURLToPath(new URL('project/', fixture.root)));
133+
createMocks({ env: { INIT_CWD: project } });
134+
vi.spyOn(console, 'info').mockImplementation(() => {});
135+
136+
await sync({ args: [] });
137+
138+
expect(await fixture.isDirectory('project/skills')).toBe(false);
139+
});
140+
});

0 commit comments

Comments
 (0)