mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-24 20:26:08 +00:00
Agent Skills cleanup (#283934)
This commit is contained in:
@@ -704,10 +704,10 @@ configurationRegistry.registerConfiguration({
|
||||
disallowConfigurationDefault: true,
|
||||
tags: ['experimental', 'prompts', 'reusable prompts', 'prompt snippets', 'instructions']
|
||||
},
|
||||
[PromptsConfig.USE_CLAUDE_SKILLS]: {
|
||||
[PromptsConfig.USE_AGENT_SKILLS]: {
|
||||
type: 'boolean',
|
||||
title: nls.localize('chat.useClaudeSkills.title', "Use Claude skills",),
|
||||
markdownDescription: nls.localize('chat.useClaudeSkills.description', "Controls whether Claude skills found in the workspace and user home directories under `.claude/skills` are listed in all chat requests. The language model can load these skills on-demand if the `read` tool is available.",),
|
||||
title: nls.localize('chat.useAgentSkills.title', "Use Agent skills",),
|
||||
markdownDescription: nls.localize('chat.useAgentSkills.description', "Controls whether Agent skills found at `.github/skills`, `.claude/skills`, `~/.claude/skills` are listed in all chat requests. The language model can load these skills on-demand if the `read` tool is available.",),
|
||||
default: false,
|
||||
restricted: true,
|
||||
disallowConfigurationDefault: true,
|
||||
@@ -862,6 +862,13 @@ Registry.as<IConfigurationMigrationRegistry>(Extensions.ConfigurationMigration).
|
||||
['chat.detectParticipant.enabled', { value: value !== false }]
|
||||
])
|
||||
},
|
||||
{
|
||||
key: 'chat.useClaudeSkills',
|
||||
migrateFn: (value, _accessor) => ([
|
||||
['chat.useClaudeSkills', { value: undefined }],
|
||||
['chat.useAgentSkills', { value }]
|
||||
])
|
||||
},
|
||||
{
|
||||
key: mcpDiscoverySection,
|
||||
migrateFn: (value: unknown) => {
|
||||
|
||||
@@ -304,13 +304,13 @@ export class ComputeAutomaticInstructions {
|
||||
entries.push('</instructions>', '', ''); // add trailing newline
|
||||
}
|
||||
|
||||
const claudeSkills = await this._promptsService.findClaudeSkills(token);
|
||||
if (claudeSkills && claudeSkills.length > 0) {
|
||||
const agentSkills = await this._promptsService.findAgentSkills(token);
|
||||
if (agentSkills && agentSkills.length > 0) {
|
||||
entries.push('<skills>');
|
||||
entries.push('Here is a list of skills that contain domain specific knowledge on a variety of topics.');
|
||||
entries.push('Each skill comes with a description of the topic and a file path that contains the detailed instructions.');
|
||||
entries.push(`When a user asks you to perform a task that falls within the domain of a skill, use the ${readTool.variable} tool to acquire the full instructions from the file URI.`);
|
||||
for (const skill of claudeSkills) {
|
||||
for (const skill of agentSkills) {
|
||||
entries.push('<skill>');
|
||||
entries.push(`<name>${skill.name}</name>`);
|
||||
if (skill.description) {
|
||||
|
||||
@@ -79,9 +79,9 @@ export namespace PromptsConfig {
|
||||
export const USE_NESTED_AGENT_MD = 'chat.useNestedAgentsMdFiles';
|
||||
|
||||
/**
|
||||
* Configuration key for claude skills usage.
|
||||
* Configuration key for agent skills usage.
|
||||
*/
|
||||
export const USE_CLAUDE_SKILLS = 'chat.useClaudeSkills';
|
||||
export const USE_AGENT_SKILLS = 'chat.useAgentSkills';
|
||||
|
||||
/**
|
||||
* Get value of the `reusable prompt locations` configuration setting.
|
||||
|
||||
@@ -53,6 +53,21 @@ export const LEGACY_MODE_DEFAULT_SOURCE_FOLDER = '.github/chatmodes';
|
||||
*/
|
||||
export const AGENTS_SOURCE_FOLDER = '.github/agents';
|
||||
|
||||
/**
|
||||
* Default agent skills workspace source folders.
|
||||
*/
|
||||
export const DEFAULT_AGENT_SKILLS_WORKSPACE_FOLDERS = [
|
||||
'.github/skills',
|
||||
'.claude/skills'
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Default agent skills user home source folders.
|
||||
*/
|
||||
export const DEFAULT_AGENT_SKILLS_USER_HOME_FOLDERS = [
|
||||
'.claude/skills'
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Helper function to check if a file is directly in the .github/agents/ folder (not in subfolders).
|
||||
*/
|
||||
|
||||
@@ -198,7 +198,7 @@ export interface IChatPromptSlashCommand {
|
||||
readonly parsedPromptFile: ParsedPromptFile;
|
||||
}
|
||||
|
||||
export interface IClaudeSkill {
|
||||
export interface IAgentSkill {
|
||||
readonly uri: URI;
|
||||
readonly type: 'personal' | 'project';
|
||||
readonly name: string;
|
||||
@@ -328,7 +328,7 @@ export interface IPromptsService extends IDisposable {
|
||||
}): IDisposable;
|
||||
|
||||
/**
|
||||
* Gets list of claude skills files.
|
||||
* Gets list of agent skills files.
|
||||
*/
|
||||
findClaudeSkills(token: CancellationToken): Promise<IClaudeSkill[] | undefined>;
|
||||
findAgentSkills(token: CancellationToken): Promise<IAgentSkill[] | undefined>;
|
||||
}
|
||||
|
||||
@@ -26,11 +26,12 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../../../
|
||||
import { IUserDataProfileService } from '../../../../../services/userDataProfile/common/userDataProfile.js';
|
||||
import { IVariableReference } from '../../chatModes.js';
|
||||
import { PromptsConfig } from '../config/config.js';
|
||||
import { IDefaultAccountService } from '../../../../../../platform/defaultAccount/common/defaultAccount.js';
|
||||
import { getCleanPromptName } from '../config/promptFileLocations.js';
|
||||
import { PROMPT_LANGUAGE_ID, PromptsType, getPromptsTypeForLanguageId } from '../promptTypes.js';
|
||||
import { PromptFilesLocator } from '../utils/promptFilesLocator.js';
|
||||
import { PromptFileParser, ParsedPromptFile, PromptHeaderAttributes } from '../promptFileParser.js';
|
||||
import { IAgentInstructions, IAgentSource, IChatPromptSlashCommand, ICustomAgent, IExtensionPromptPath, ILocalPromptPath, IPromptPath, IPromptsService, IClaudeSkill, IUserPromptPath, PromptsStorage, ICustomAgentQueryOptions, IExternalCustomAgent, ExtensionAgentSourceType, CUSTOM_AGENTS_PROVIDER_ACTIVATION_EVENT } from './promptsService.js';
|
||||
import { IAgentInstructions, IAgentSource, IChatPromptSlashCommand, ICustomAgent, IExtensionPromptPath, ILocalPromptPath, IPromptPath, IPromptsService, IAgentSkill, IUserPromptPath, PromptsStorage, ICustomAgentQueryOptions, IExternalCustomAgent, ExtensionAgentSourceType, CUSTOM_AGENTS_PROVIDER_ACTIVATION_EVENT } from './promptsService.js';
|
||||
import { Delayer } from '../../../../../../base/common/async.js';
|
||||
import { Schemas } from '../../../../../../base/common/network.js';
|
||||
|
||||
@@ -93,7 +94,8 @@ export class PromptsService extends Disposable implements IPromptsService {
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IFilesConfigurationService private readonly filesConfigService: IFilesConfigurationService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IDefaultAccountService private readonly defaultAccountService: IDefaultAccountService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -575,29 +577,31 @@ export class PromptsService extends Disposable implements IPromptsService {
|
||||
}
|
||||
}
|
||||
|
||||
// Claude skills
|
||||
// Agent skills
|
||||
|
||||
public async findClaudeSkills(token: CancellationToken): Promise<IClaudeSkill[] | undefined> {
|
||||
const useClaudeSkills = this.configurationService.getValue(PromptsConfig.USE_CLAUDE_SKILLS);
|
||||
if (useClaudeSkills) {
|
||||
const result: IClaudeSkill[] = [];
|
||||
public async findAgentSkills(token: CancellationToken): Promise<IAgentSkill[] | undefined> {
|
||||
const useAgentSkills = this.configurationService.getValue(PromptsConfig.USE_AGENT_SKILLS);
|
||||
const defaultAccount = await this.defaultAccountService.getDefaultAccount();
|
||||
const previewFeaturesEnabled = defaultAccount?.chat_preview_features_enabled ?? true;
|
||||
if (useAgentSkills && previewFeaturesEnabled) {
|
||||
const result: IAgentSkill[] = [];
|
||||
const process = async (uri: URI, type: 'personal' | 'project'): Promise<void> => {
|
||||
try {
|
||||
const parsedFile = await this.parseNew(uri, token);
|
||||
const name = parsedFile.header?.name;
|
||||
if (name) {
|
||||
result.push({ uri, type, name, description: parsedFile.header?.description } satisfies IClaudeSkill);
|
||||
result.push({ uri, type, name, description: parsedFile.header?.description } satisfies IAgentSkill);
|
||||
} else {
|
||||
this.logger.error(`[findClaudeSkills] Claude skill file missing name attribute: ${uri}`);
|
||||
this.logger.error(`[findAgentSkills] Agent skill file missing name attribute: ${uri}`);
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error(`[findClaudeSkills] Failed to parse Claude skill file: ${uri}`, e instanceof Error ? e.message : String(e));
|
||||
this.logger.error(`[findAgentSkills] Failed to parse Agent skill file: ${uri}`, e instanceof Error ? e.message : String(e));
|
||||
}
|
||||
};
|
||||
|
||||
const workspaceSkills = await this.fileLocator.findClaudeSkillsInWorkspace(token);
|
||||
const workspaceSkills = await this.fileLocator.findAgentSkillsInWorkspace(token);
|
||||
await Promise.all(workspaceSkills.map(uri => process(uri, 'project')));
|
||||
const userSkills = await this.fileLocator.findClaudeSkillsInUserHome(token);
|
||||
const userSkills = await this.fileLocator.findAgentSkillsInUserHome(token);
|
||||
await Promise.all(userSkills.map(uri => process(uri, 'personal')));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { getPromptFileLocationsConfigKey, PromptsConfig } from '../config/config
|
||||
import { basename, dirname, isEqualOrParent, joinPath } from '../../../../../../base/common/resources.js';
|
||||
import { IWorkspaceContextService } from '../../../../../../platform/workspace/common/workspace.js';
|
||||
import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js';
|
||||
import { COPILOT_CUSTOM_INSTRUCTIONS_FILENAME, AGENTS_SOURCE_FOLDER, getPromptFileExtension, getPromptFileType, LEGACY_MODE_FILE_EXTENSION, getCleanPromptName, AGENT_FILE_EXTENSION } from '../config/promptFileLocations.js';
|
||||
import { COPILOT_CUSTOM_INSTRUCTIONS_FILENAME, AGENTS_SOURCE_FOLDER, getPromptFileExtension, getPromptFileType, LEGACY_MODE_FILE_EXTENSION, getCleanPromptName, AGENT_FILE_EXTENSION, DEFAULT_AGENT_SKILLS_WORKSPACE_FOLDERS, DEFAULT_AGENT_SKILLS_USER_HOME_FOLDERS } from '../config/promptFileLocations.js';
|
||||
import { PromptsType } from '../promptTypes.js';
|
||||
import { IWorkbenchEnvironmentService } from '../../../../../services/environment/common/environmentService.js';
|
||||
import { Schemas } from '../../../../../../base/common/network.js';
|
||||
@@ -366,10 +366,10 @@ export class PromptFilesLocator {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async findClaudeSkillsInFolder(uri: URI, token: CancellationToken): Promise<URI[]> {
|
||||
private async findAgentSkillsInFolder(uri: URI, relativePath: string, token: CancellationToken): Promise<URI[]> {
|
||||
const result = [];
|
||||
try {
|
||||
const stat = await this.fileService.resolve(joinPath(uri, '.claude/skills'));
|
||||
const stat = await this.fileService.resolve(joinPath(uri, relativePath));
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
@@ -392,22 +392,33 @@ export class PromptFilesLocator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for skills in `.claude/skills/` directories in the workspace.
|
||||
* Searches for skills in all default directories in the workspace.
|
||||
* Each skill is stored in its own subdirectory with a SKILL.md file.
|
||||
*/
|
||||
public async findClaudeSkillsInWorkspace(token: CancellationToken): Promise<URI[]> {
|
||||
public async findAgentSkillsInWorkspace(token: CancellationToken): Promise<URI[]> {
|
||||
const workspace = this.workspaceService.getWorkspace();
|
||||
const results = await Promise.all(workspace.folders.map(f => this.findClaudeSkillsInFolder(f.uri, token)));
|
||||
return results.flat();
|
||||
const allResults: URI[] = [];
|
||||
for (const folder of workspace.folders) {
|
||||
for (const skillsFolder of DEFAULT_AGENT_SKILLS_WORKSPACE_FOLDERS) {
|
||||
const results = await this.findAgentSkillsInFolder(folder.uri, skillsFolder, token);
|
||||
allResults.push(...results);
|
||||
}
|
||||
}
|
||||
return allResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for skills in `.claude/skills/` directories in the home folder.
|
||||
* Searches for skills in all default directories in the home folder.
|
||||
* Each skill is stored in its own subdirectory with a SKILL.md file.
|
||||
*/
|
||||
public async findClaudeSkillsInUserHome(token: CancellationToken): Promise<URI[]> {
|
||||
public async findAgentSkillsInUserHome(token: CancellationToken): Promise<URI[]> {
|
||||
const userHome = await this.pathService.userHome();
|
||||
return this.findClaudeSkillsInFolder(userHome, token);
|
||||
const allResults: URI[] = [];
|
||||
for (const skillsFolder of DEFAULT_AGENT_SKILLS_USER_HOME_FOLDERS) {
|
||||
const results = await this.findAgentSkillsInFolder(userHome, skillsFolder, token);
|
||||
allResults.push(...results);
|
||||
}
|
||||
return allResults;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { ITextModel } from '../../../../../editor/common/model.js';
|
||||
import { IExtensionDescription } from '../../../../../platform/extensions/common/extensions.js';
|
||||
import { PromptsType } from '../../common/promptSyntax/promptTypes.js';
|
||||
import { ParsedPromptFile } from '../../common/promptSyntax/promptFileParser.js';
|
||||
import { IClaudeSkill, ICustomAgent, ICustomAgentQueryOptions, IExternalCustomAgent, IPromptPath, IPromptsService, PromptsStorage } from '../../common/promptSyntax/service/promptsService.js';
|
||||
import { IAgentSkill, ICustomAgent, ICustomAgentQueryOptions, IExternalCustomAgent, IPromptPath, IPromptsService, PromptsStorage } from '../../common/promptSyntax/service/promptsService.js';
|
||||
import { ResourceSet } from '../../../../../base/common/map.js';
|
||||
|
||||
export class MockPromptsService implements IPromptsService {
|
||||
@@ -61,6 +61,6 @@ export class MockPromptsService implements IPromptsService {
|
||||
getDisabledPromptFiles(type: PromptsType): ResourceSet { throw new Error('Method not implemented.'); }
|
||||
setDisabledPromptFiles(type: PromptsType, uris: ResourceSet): void { throw new Error('Method not implemented.'); }
|
||||
registerCustomAgentsProvider(extension: IExtensionDescription, provider: { provideCustomAgents: (options: ICustomAgentQueryOptions, token: CancellationToken) => Promise<IExternalCustomAgent[] | undefined> }): IDisposable { throw new Error('Method not implemented.'); }
|
||||
findClaudeSkills(token: CancellationToken): Promise<IClaudeSkill[] | undefined> { throw new Error('Method not implemented.'); }
|
||||
findAgentSkills(token: CancellationToken): Promise<IAgentSkill[] | undefined> { throw new Error('Method not implemented.'); }
|
||||
dispose(): void { }
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ import { InMemoryStorageService, IStorageService } from '../../../../../../../pl
|
||||
import { IPathService } from '../../../../../../services/path/common/pathService.js';
|
||||
import { ISearchService } from '../../../../../../services/search/common/search.js';
|
||||
import { IExtensionService } from '../../../../../../services/extensions/common/extensions.js';
|
||||
import { IDefaultAccountService } from '../../../../../../../platform/defaultAccount/common/defaultAccount.js';
|
||||
import { IDefaultAccount } from '../../../../../../../base/common/defaultAccount.js';
|
||||
|
||||
suite('PromptsService', () => {
|
||||
const disposables = ensureNoDisposablesAreLeakedInTestSuite();
|
||||
@@ -78,6 +80,10 @@ suite('PromptsService', () => {
|
||||
activateByEvent: () => Promise.resolve()
|
||||
});
|
||||
|
||||
instaService.stub(IDefaultAccountService, {
|
||||
getDefaultAccount: () => Promise.resolve({ chat_preview_features_enabled: true } as IDefaultAccount)
|
||||
});
|
||||
|
||||
fileService = disposables.add(instaService.createInstance(FileService));
|
||||
instaService.stub(IFileService, fileService);
|
||||
|
||||
@@ -1255,50 +1261,96 @@ suite('PromptsService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
suite('findClaudeSkills', () => {
|
||||
suite('findAgentSkills', () => {
|
||||
teardown(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
test('should return undefined when USE_CLAUDE_SKILLS is disabled', async () => {
|
||||
testConfigService.setUserConfiguration(PromptsConfig.USE_CLAUDE_SKILLS, false);
|
||||
test('should return undefined when USE_AGENT_SKILLS is disabled', async () => {
|
||||
testConfigService.setUserConfiguration(PromptsConfig.USE_AGENT_SKILLS, false);
|
||||
|
||||
const result = await service.findClaudeSkills(CancellationToken.None);
|
||||
const result = await service.findAgentSkills(CancellationToken.None);
|
||||
assert.strictEqual(result, undefined);
|
||||
});
|
||||
|
||||
test('should find Claude skills in workspace and user home', async () => {
|
||||
testConfigService.setUserConfiguration(PromptsConfig.USE_CLAUDE_SKILLS, true);
|
||||
test('should return undefined when chat_preview_features_enabled is false', async () => {
|
||||
testConfigService.setUserConfiguration(PromptsConfig.USE_AGENT_SKILLS, true);
|
||||
instaService.stub(IDefaultAccountService, {
|
||||
getDefaultAccount: () => Promise.resolve({ chat_preview_features_enabled: false } as IDefaultAccount)
|
||||
});
|
||||
|
||||
const rootFolderName = 'claude-skills-test';
|
||||
// Recreate service with new stub
|
||||
service = disposables.add(instaService.createInstance(PromptsService));
|
||||
|
||||
const result = await service.findAgentSkills(CancellationToken.None);
|
||||
assert.strictEqual(result, undefined);
|
||||
|
||||
// Restore default stub for other tests
|
||||
instaService.stub(IDefaultAccountService, {
|
||||
getDefaultAccount: () => Promise.resolve({ chat_preview_features_enabled: true } as IDefaultAccount)
|
||||
});
|
||||
});
|
||||
|
||||
test('should return undefined when USE_AGENT_SKILLS is enabled but chat_preview_features_enabled is false', async () => {
|
||||
testConfigService.setUserConfiguration(PromptsConfig.USE_AGENT_SKILLS, true);
|
||||
instaService.stub(IDefaultAccountService, {
|
||||
getDefaultAccount: () => Promise.resolve({ chat_preview_features_enabled: false } as IDefaultAccount)
|
||||
});
|
||||
|
||||
// Recreate service with new stub
|
||||
service = disposables.add(instaService.createInstance(PromptsService));
|
||||
|
||||
const result = await service.findAgentSkills(CancellationToken.None);
|
||||
assert.strictEqual(result, undefined);
|
||||
|
||||
// Restore default stub for other tests
|
||||
instaService.stub(IDefaultAccountService, {
|
||||
getDefaultAccount: () => Promise.resolve({ chat_preview_features_enabled: true } as IDefaultAccount)
|
||||
});
|
||||
});
|
||||
|
||||
test('should find skills in workspace and user home', async () => {
|
||||
testConfigService.setUserConfiguration(PromptsConfig.USE_AGENT_SKILLS, true);
|
||||
|
||||
const rootFolderName = 'agent-skills-test';
|
||||
const rootFolder = `/${rootFolderName}`;
|
||||
const rootFolderUri = URI.file(rootFolder);
|
||||
|
||||
workspaceContextService.setWorkspace(testWorkspace(rootFolderUri));
|
||||
|
||||
// Create mock filesystem with skills
|
||||
// Create mock filesystem with skills in both .github/skills and .claude/skills
|
||||
await mockFiles(fileService, [
|
||||
{
|
||||
path: `${rootFolder}/.claude/skills/project-skill-1/SKILL.md`,
|
||||
path: `${rootFolder}/.github/skills/github-skill-1/SKILL.md`,
|
||||
contents: [
|
||||
'---',
|
||||
'name: "Project Skill 1"',
|
||||
'description: "A project skill for testing"',
|
||||
'name: "GitHub Skill 1"',
|
||||
'description: "A GitHub skill for testing"',
|
||||
'---',
|
||||
'This is project skill 1 content',
|
||||
'This is GitHub skill 1 content',
|
||||
],
|
||||
},
|
||||
{
|
||||
path: `${rootFolder}/.claude/skills/project-skill-2/SKILL.md`,
|
||||
path: `${rootFolder}/.claude/skills/claude-skill-1/SKILL.md`,
|
||||
contents: [
|
||||
'---',
|
||||
'name: "Claude Skill 1"',
|
||||
'description: "A Claude skill for testing"',
|
||||
'---',
|
||||
'This is Claude skill 1 content',
|
||||
],
|
||||
},
|
||||
{
|
||||
path: `${rootFolder}/.claude/skills/invalid-skill/SKILL.md`,
|
||||
contents: [
|
||||
'---',
|
||||
'description: "Invalid skill, no name"',
|
||||
'---',
|
||||
'This is project skill 2 content',
|
||||
'This is invalid skill content',
|
||||
],
|
||||
},
|
||||
{
|
||||
path: `${rootFolder}/.claude/skills/not-a-skill-dir/README.md`,
|
||||
path: `${rootFolder}/.github/skills/not-a-skill-dir/README.md`,
|
||||
contents: ['This is not a skill'],
|
||||
},
|
||||
{
|
||||
@@ -1317,19 +1369,24 @@ suite('PromptsService', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await service.findClaudeSkills(CancellationToken.None);
|
||||
const result = await service.findAgentSkills(CancellationToken.None);
|
||||
|
||||
assert.ok(result, 'Should return results when Claude skills are enabled');
|
||||
assert.strictEqual(result.length, 2, 'Should find 2 skills total');
|
||||
assert.ok(result, 'Should return results when agent skills are enabled');
|
||||
assert.strictEqual(result.length, 3, 'Should find 3 skills total');
|
||||
|
||||
// Check project skills
|
||||
// Check project skills (both from .github/skills and .claude/skills)
|
||||
const projectSkills = result.filter(skill => skill.type === 'project');
|
||||
assert.strictEqual(projectSkills.length, 1, 'Should find 1 project skill');
|
||||
assert.strictEqual(projectSkills.length, 2, 'Should find 2 project skills');
|
||||
|
||||
const projectSkill1 = projectSkills.find(skill => skill.name === 'Project Skill 1');
|
||||
assert.ok(projectSkill1, 'Should find project skill 1');
|
||||
assert.strictEqual(projectSkill1.description, 'A project skill for testing');
|
||||
assert.strictEqual(projectSkill1.uri.path, `${rootFolder}/.claude/skills/project-skill-1/SKILL.md`);
|
||||
const githubSkill1 = projectSkills.find(skill => skill.name === 'GitHub Skill 1');
|
||||
assert.ok(githubSkill1, 'Should find GitHub skill 1');
|
||||
assert.strictEqual(githubSkill1.description, 'A GitHub skill for testing');
|
||||
assert.strictEqual(githubSkill1.uri.path, `${rootFolder}/.github/skills/github-skill-1/SKILL.md`);
|
||||
|
||||
const claudeSkill1 = projectSkills.find(skill => skill.name === 'Claude Skill 1');
|
||||
assert.ok(claudeSkill1, 'Should find Claude skill 1');
|
||||
assert.strictEqual(claudeSkill1.description, 'A Claude skill for testing');
|
||||
assert.strictEqual(claudeSkill1.uri.path, `${rootFolder}/.claude/skills/claude-skill-1/SKILL.md`);
|
||||
|
||||
// Check personal skills
|
||||
const personalSkills = result.filter(skill => skill.type === 'personal');
|
||||
@@ -1342,18 +1399,18 @@ suite('PromptsService', () => {
|
||||
});
|
||||
|
||||
test('should handle parsing errors gracefully', async () => {
|
||||
testConfigService.setUserConfiguration(PromptsConfig.USE_CLAUDE_SKILLS, true);
|
||||
testConfigService.setUserConfiguration(PromptsConfig.USE_AGENT_SKILLS, true);
|
||||
|
||||
const rootFolderName = 'claude-skills-error-test';
|
||||
const rootFolderName = 'skills-error-test';
|
||||
const rootFolder = `/${rootFolderName}`;
|
||||
const rootFolderUri = URI.file(rootFolder);
|
||||
|
||||
workspaceContextService.setWorkspace(testWorkspace(rootFolderUri));
|
||||
|
||||
// Create mock filesystem with malformed skill file
|
||||
// Create mock filesystem with malformed skill file in .github/skills
|
||||
await mockFiles(fileService, [
|
||||
{
|
||||
path: `${rootFolder}/.claude/skills/valid-skill/SKILL.md`,
|
||||
path: `${rootFolder}/.github/skills/valid-skill/SKILL.md`,
|
||||
contents: [
|
||||
'---',
|
||||
'name: "Valid Skill"',
|
||||
@@ -1373,7 +1430,7 @@ suite('PromptsService', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await service.findClaudeSkills(CancellationToken.None);
|
||||
const result = await service.findAgentSkills(CancellationToken.None);
|
||||
|
||||
// Should still return the valid skill, even if one has parsing errors
|
||||
assert.ok(result, 'Should return results even with parsing errors');
|
||||
@@ -1383,7 +1440,7 @@ suite('PromptsService', () => {
|
||||
});
|
||||
|
||||
test('should return empty array when no skills found', async () => {
|
||||
testConfigService.setUserConfiguration(PromptsConfig.USE_CLAUDE_SKILLS, true);
|
||||
testConfigService.setUserConfiguration(PromptsConfig.USE_AGENT_SKILLS, true);
|
||||
|
||||
const rootFolderName = 'empty-workspace';
|
||||
const rootFolder = `/${rootFolderName}`;
|
||||
@@ -1394,7 +1451,7 @@ suite('PromptsService', () => {
|
||||
// Create empty mock filesystem
|
||||
await mockFiles(fileService, []);
|
||||
|
||||
const result = await service.findClaudeSkills(CancellationToken.None);
|
||||
const result = await service.findAgentSkills(CancellationToken.None);
|
||||
|
||||
assert.ok(result, 'Should return results array');
|
||||
assert.strictEqual(result.length, 0, 'Should find no skills');
|
||||
|
||||
Reference in New Issue
Block a user