Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .changeset/skill-scanner-test-portability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

Make skill scanner path assertions match the normalized paths returned by `resolveSkillRoots` on Windows.
84 changes: 48 additions & 36 deletions packages/agent-core/test/skill/scanner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ afterEach(async () => {
}
});

async function normalizedRealpath(p: string): Promise<string> {
return (await realpath(p)).replaceAll('\\', '/');
}

describe('skill discovery', () => {
it('resolves documented roots in precedence order with brand merging enabled by default', async () => {
const { homeDir, repoDir, workDir } = await makeWorkspace();
Expand All @@ -22,7 +26,7 @@ describe('skill discovery', () => {
await mkdir(path.join(homeDir, '.kimi-code', 'skills'), { recursive: true });
await mkdir(path.join(homeDir, '.agents', 'skills'), { recursive: true });
await mkdir(path.join(repoDir, 'team-skills'), { recursive: true });
const realRepoDir = await realpath(repoDir);
const realRepoDir = await normalizedRealpath(repoDir);

const roots = await resolveSkillRoots({
paths: { userHomeDir: homeDir, workDir },
Expand All @@ -32,8 +36,14 @@ describe('skill discovery', () => {
expect(roots.map((root) => path.relative(realRepoDir, root.path))).toEqual([
'.kimi-code/skills',
'.agents/skills',
path.relative(realRepoDir, await realpath(path.join(homeDir, '.kimi-code', 'skills'))),
path.relative(realRepoDir, await realpath(path.join(homeDir, '.agents', 'skills'))),
path.relative(
realRepoDir,
await normalizedRealpath(path.join(homeDir, '.kimi-code', 'skills')),
),
path.relative(
realRepoDir,
await normalizedRealpath(path.join(homeDir, '.agents', 'skills')),
),
'team-skills',
]);
expect(roots.map((root) => root.source)).toEqual([
Expand All @@ -56,8 +66,8 @@ describe('skill discovery', () => {
});

expect(roots.map((root) => root.path)).toEqual([
await realpath(path.join(repoDir, '.kimi-code', 'skills')),
await realpath(path.join(homeDir, '.kimi-code', 'skills')),
await normalizedRealpath(path.join(repoDir, '.kimi-code', 'skills')),
await normalizedRealpath(path.join(homeDir, '.kimi-code', 'skills')),
]);
});

Expand All @@ -73,7 +83,7 @@ describe('skill discovery', () => {
explicitDirs: ['explicit-skills'],
extraDirs: ['extra-skills'],
});
const realRepoDir = await realpath(repoDir);
const realRepoDir = await normalizedRealpath(repoDir);

expect(roots.map((root) => [path.relative(realRepoDir, root.path), root.source])).toEqual([
['explicit-skills', 'user'],
Expand Down Expand Up @@ -541,11 +551,11 @@ describe('resolveSkillRoots ordering and priority', () => {
});

expect(roots.map((r) => r.path)).toEqual([
await realpath(projBrand),
await realpath(projGeneric),
await realpath(userBrand),
await realpath(userGeneric),
await realpath(builtin),
await normalizedRealpath(projBrand),
await normalizedRealpath(projGeneric),
await normalizedRealpath(userBrand),
await normalizedRealpath(userGeneric),
await normalizedRealpath(builtin),
]);
});

Expand Down Expand Up @@ -574,7 +584,9 @@ describe('resolveSkillRoots ordering and priority', () => {
const roots = await resolveSkillRoots({ paths: { userHomeDir: homeDir, workDir } });

const paths = roots.map((r) => r.path);
expect(paths).toContain(await realpath(path.join(homeDir, '.kimi-code', 'skills')));
expect(paths).toContain(
await normalizedRealpath(path.join(homeDir, '.kimi-code', 'skills')),
);
});
});

Expand All @@ -589,7 +601,7 @@ describe('resolveSkillRoots extra dirs', () => {
extraDirs: [extra],
});

expect(roots.map((r) => r.path)).toContain(await realpath(extra));
expect(roots.map((r) => r.path)).toContain(await normalizedRealpath(extra));
});

it('expands a leading ~/ in extra dirs against the user home directory', async () => {
Expand All @@ -602,7 +614,7 @@ describe('resolveSkillRoots extra dirs', () => {
extraDirs: ['~/my-skills'],
});

expect(roots.map((r) => r.path)).toContain(await realpath(target));
expect(roots.map((r) => r.path)).toContain(await normalizedRealpath(target));
});

it('resolves a relative extra dir against the project root (.git ancestor), not the work dir', async () => {
Expand All @@ -618,7 +630,7 @@ describe('resolveSkillRoots extra dirs', () => {
});

const paths = roots.map((r) => r.path);
expect(paths).toContain(await realpath(extraAtRoot));
expect(paths).toContain(await normalizedRealpath(extraAtRoot));
expect(paths).not.toContain(path.join(nested, 'my-dir'));
});

Expand All @@ -632,7 +644,7 @@ describe('resolveSkillRoots extra dirs', () => {
extraDirs: [absExtra],
});

expect(roots.map((r) => r.path)).toContain(await realpath(absExtra));
expect(roots.map((r) => r.path)).toContain(await normalizedRealpath(absExtra));
});

it('silently drops missing extra-dir entries', async () => {
Expand All @@ -646,7 +658,7 @@ describe('resolveSkillRoots extra dirs', () => {
});

const paths = roots.map((r) => r.path);
expect(paths).toContain(await realpath(real));
expect(paths).toContain(await normalizedRealpath(real));
expect(paths).not.toContain(path.join(repoDir, 'nowhere'));
});

Expand All @@ -660,7 +672,7 @@ describe('resolveSkillRoots extra dirs', () => {
extraDirs: [real, real],
});

const realResolved = await realpath(real);
const realResolved = await normalizedRealpath(real);
const matches = roots.filter((r) => r.path === realResolved);
expect(matches).toHaveLength(1);
});
Expand All @@ -685,7 +697,7 @@ describe('resolveSkillRoots extra dirs', () => {
],
});

const realResolved = await realpath(real);
const realResolved = await normalizedRealpath(real);
const matches = roots.filter((r) => r.path === realResolved);
expect(matches).toHaveLength(1);
expect(matches[0]?.plugin).toEqual({
Expand Down Expand Up @@ -769,10 +781,10 @@ describe('resolveSkillRoots extra dirs', () => {
});

const paths = roots.map((r) => r.path);
expect(paths).toContain(await realpath(cli));
expect(paths).toContain(await realpath(extra));
expect(paths).not.toContain(await realpath(userBrand));
expect(paths).not.toContain(await realpath(projectBrand));
expect(paths).toContain(await normalizedRealpath(cli));
expect(paths).toContain(await normalizedRealpath(extra));
expect(paths).not.toContain(await normalizedRealpath(userBrand));
expect(paths).not.toContain(await normalizedRealpath(projectBrand));
});

it('collapses a real dir and a symlink to the same target into one root', async () => {
Expand All @@ -789,7 +801,7 @@ describe('resolveSkillRoots extra dirs', () => {

const extras = roots.filter((r) => r.source === 'extra');
expect(extras).toHaveLength(1);
expect(extras[0]?.path).toBe(await realpath(real));
expect(extras[0]?.path).toBe(await normalizedRealpath(real));
});

it('collapses entries differing only by trailing slash', async () => {
Expand Down Expand Up @@ -837,7 +849,7 @@ describe('resolveSkillRoots extra dirs', () => {

const extras = roots.filter((r) => r.source === 'extra');
expect(extras).toHaveLength(1);
expect(extras[0]?.path).toBe(await realpath(real));
expect(extras[0]?.path).toBe(await normalizedRealpath(real));
expect(extras[0]?.path).not.toBe(link);
});

Expand All @@ -857,7 +869,7 @@ describe('resolveSkillRoots extra dirs', () => {
extraDirs: [userBrand],
});

const realUserBrand = await realpath(userBrand);
const realUserBrand = await normalizedRealpath(userBrand);
const matching = roots.filter((r) => r.path === realUserBrand);
expect(matching).toHaveLength(1);
expect(matching[0]?.source).toBe('user');
Expand Down Expand Up @@ -1125,11 +1137,11 @@ describe('explicit dir override and scope stamping', () => {
});

const paths = roots.map((r) => r.path);
expect(paths).toContain(await realpath(extraA));
expect(paths).toContain(await realpath(extraB));
expect(paths).toContain(await realpath(builtin));
expect(paths).not.toContain(await realpath(userBrand));
expect(paths).not.toContain(await realpath(projBrand));
expect(paths).toContain(await normalizedRealpath(extraA));
expect(paths).toContain(await normalizedRealpath(extraB));
expect(paths).toContain(await normalizedRealpath(builtin));
expect(paths).not.toContain(await normalizedRealpath(userBrand));
expect(paths).not.toContain(await normalizedRealpath(projBrand));
});

it('returns both brand and generic user dirs when generic is empty (no shadowing)', async () => {
Expand All @@ -1142,10 +1154,10 @@ describe('explicit dir override and scope stamping', () => {
const roots = await resolveSkillRoots({ paths: { userHomeDir: homeDir, workDir } });
const userPaths = roots.filter((r) => r.source === 'user').map((r) => r.path);

expect(userPaths).toContain(await realpath(brand));
expect(userPaths).toContain(await realpath(generic));
expect(userPaths.indexOf(await realpath(brand))).toBeLessThan(
userPaths.indexOf(await realpath(generic)),
expect(userPaths).toContain(await normalizedRealpath(brand));
expect(userPaths).toContain(await normalizedRealpath(generic));
expect(userPaths.indexOf(await normalizedRealpath(brand))).toBeLessThan(
userPaths.indexOf(await normalizedRealpath(generic)),
);
});

Expand Down Expand Up @@ -1211,7 +1223,7 @@ describe('project root discovery (.git walk-up)', () => {
});
const projectPaths = roots.filter((r) => r.source === 'project').map((r) => r.path);

expect(projectPaths).toContain(await realpath(repoKimi));
expect(projectPaths).toContain(await normalizedRealpath(repoKimi));
});

it('falls back to the work dir when no .git marker is found anywhere up the chain', async () => {
Expand Down