mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
feat: add ~/.claude/skills/ and .claude/skills/ directory scanning support (#288)
This commit is contained in:
@@ -50,6 +50,25 @@ describe('listSkills', () => {
|
||||
expect(skills.map((skill) => skill.name)).toEqual(['amis'])
|
||||
})
|
||||
|
||||
it('lists user skills from ~/.claude/skills', async () => {
|
||||
await writeSkill(join(homeDir, '.claude', 'skills', 'claude-skill'), 'claude-skill', 'Claude skill')
|
||||
|
||||
const skills = await listSkills()
|
||||
|
||||
expect(skills.map((skill) => skill.name)).toEqual(['claude-skill'])
|
||||
})
|
||||
|
||||
it('merges user skills from ~/.agents and ~/.claude, preferring ~/.agents', async () => {
|
||||
await writeSkill(join(homeDir, '.agents', 'skills', 'alpha'), 'alpha', 'Alpha from agents')
|
||||
await writeSkill(join(homeDir, '.claude', 'skills', 'beta'), 'beta', 'Beta from claude')
|
||||
await writeSkill(join(homeDir, '.claude', 'skills', 'alpha'), 'alpha', 'Alpha from claude')
|
||||
|
||||
const skills = await listSkills()
|
||||
|
||||
expect(skills.map((skill) => skill.name)).toEqual(['alpha', 'beta'])
|
||||
expect(skills.find((s) => s.name === 'alpha')?.description).toBe('Alpha from agents')
|
||||
})
|
||||
|
||||
it('ignores legacy ~/.codex skills', async () => {
|
||||
await writeSkill(join(homeDir, '.agents', 'skills', 'amis'), 'amis', 'AMIS guide')
|
||||
await writeSkill(join(homeDir, '.codex', 'skills', 'hello-agents'), 'helloagents', 'Main skill')
|
||||
@@ -86,6 +105,33 @@ describe('listSkills', () => {
|
||||
expect(skills.map((skill) => skill.name)).toEqual(['local-skill', 'package-skill', 'root-skill'])
|
||||
})
|
||||
|
||||
it('loads project skills from .claude/skills directories', async () => {
|
||||
const repoRoot = join(sandboxDir, 'repo')
|
||||
const workingDirectory = join(repoRoot, 'apps', 'web')
|
||||
|
||||
await mkdir(join(repoRoot, '.git'), { recursive: true })
|
||||
await writeSkill(join(repoRoot, '.claude', 'skills', 'claude-root'), 'claude-root', 'Claude root skill')
|
||||
await writeSkill(join(workingDirectory, '.claude', 'skills', 'claude-local'), 'claude-local', 'Claude local skill')
|
||||
|
||||
const skills = await listSkills(workingDirectory)
|
||||
|
||||
expect(skills.map((skill) => skill.name)).toEqual(['claude-local', 'claude-root'])
|
||||
})
|
||||
|
||||
it('prefers .agents project skills over .claude project skills with same name', async () => {
|
||||
const repoRoot = join(sandboxDir, 'repo')
|
||||
const workingDirectory = join(repoRoot, 'apps', 'web')
|
||||
|
||||
await mkdir(join(repoRoot, '.git'), { recursive: true })
|
||||
await writeSkill(join(workingDirectory, '.agents', 'skills', 'shared'), 'shared', 'From agents')
|
||||
await writeSkill(join(workingDirectory, '.claude', 'skills', 'shared'), 'shared', 'From claude')
|
||||
|
||||
const skills = await listSkills(workingDirectory)
|
||||
|
||||
expect(skills).toHaveLength(1)
|
||||
expect(skills[0]).toEqual({ name: 'shared', description: 'From agents' })
|
||||
})
|
||||
|
||||
it('uses only cwd project skills outside a git repository', async () => {
|
||||
const parentDirectory = join(sandboxDir, 'workspace')
|
||||
const workingDirectory = join(parentDirectory, 'feature')
|
||||
|
||||
@@ -21,16 +21,23 @@ function getHomeDirectory(): string {
|
||||
return process.env.HOME ?? process.env.USERPROFILE ?? homedir();
|
||||
}
|
||||
|
||||
function getUserSkillsRoot(): string {
|
||||
return join(getHomeDirectory(), '.agents', 'skills');
|
||||
function getUserSkillsRoots(): string[] {
|
||||
const home = getHomeDirectory();
|
||||
return [
|
||||
join(home, '.agents', 'skills'),
|
||||
join(home, '.claude', 'skills'),
|
||||
];
|
||||
}
|
||||
|
||||
function getAdminSkillsRoot(): string {
|
||||
return join('/etc', 'codex', 'skills');
|
||||
}
|
||||
|
||||
function getProjectSkillsRoot(directory: string): string {
|
||||
return join(directory, '.agents', 'skills');
|
||||
function getProjectSkillsRoots(directory: string): string[] {
|
||||
return [
|
||||
join(directory, '.agents', 'skills'),
|
||||
join(directory, '.claude', 'skills'),
|
||||
];
|
||||
}
|
||||
|
||||
async function pathExists(path: string): Promise<boolean> {
|
||||
@@ -53,12 +60,12 @@ async function listProjectSkillsRoots(workingDirectory?: string): Promise<string
|
||||
|
||||
while (true) {
|
||||
if (await pathExists(join(currentDirectory, '.git'))) {
|
||||
return directories.map(getProjectSkillsRoot);
|
||||
return directories.flatMap(getProjectSkillsRoots);
|
||||
}
|
||||
|
||||
const parentDirectory = dirname(currentDirectory);
|
||||
if (parentDirectory === currentDirectory) {
|
||||
return [getProjectSkillsRoot(resolvedWorkingDirectory)];
|
||||
return getProjectSkillsRoots(resolvedWorkingDirectory);
|
||||
}
|
||||
|
||||
currentDirectory = parentDirectory;
|
||||
@@ -134,7 +141,7 @@ export async function listSkills(workingDirectory?: string): Promise<SkillSummar
|
||||
const projectRoots = await listProjectSkillsRoots(workingDirectory);
|
||||
const [projectSkillDirs, userSkillDirs, adminSkillDirs] = await Promise.all([
|
||||
Promise.all(projectRoots.map(async (root) => await listTopLevelSkillDirs(root))).then((dirs) => dirs.flat()),
|
||||
listTopLevelSkillDirs(getUserSkillsRoot()),
|
||||
Promise.all(getUserSkillsRoots().map(async (root) => await listTopLevelSkillDirs(root))).then((dirs) => dirs.flat()),
|
||||
listTopLevelSkillDirs(getAdminSkillsRoot()),
|
||||
]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user