mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 14:10:37 -07:00
refactor(skills): replace 'project' with 'workspace' scope (#16380)
This commit is contained in:
@@ -13,7 +13,7 @@ discoverable capability.
|
||||
## Overview
|
||||
|
||||
Unlike general context files ([`GEMINI.md`](./gemini-md.md)), which provide
|
||||
persistent project-wide background, Skills represent **on-demand expertise**.
|
||||
persistent workspace-wide background, Skills represent **on-demand expertise**.
|
||||
This allows Gemini to maintain a vast library of specialized capabilities—such
|
||||
as security auditing, cloud deployments, or codebase migrations—without
|
||||
cluttering the model's immediate context window.
|
||||
@@ -39,15 +39,15 @@ the full instructions and resources required to complete the task using the
|
||||
|
||||
Gemini CLI discovers skills from three primary locations:
|
||||
|
||||
1. **Project Skills** (`.gemini/skills/`): Project-specific skills that are
|
||||
1. **Workspace Skills** (`.gemini/skills/`): Workspace-specific skills that are
|
||||
typically committed to version control and shared with the team.
|
||||
2. **User Skills** (`~/.gemini/skills/`): Personal skills available across all
|
||||
your projects.
|
||||
your workspaces.
|
||||
3. **Extension Skills**: Skills bundled within installed
|
||||
[extensions](../extensions/index.md).
|
||||
|
||||
**Precedence:** If multiple skills share the same name, higher-precedence
|
||||
locations override lower ones: **Project > User > Extension**.
|
||||
locations override lower ones: **Workspace > User > Extension**.
|
||||
|
||||
## Managing Skills
|
||||
|
||||
@@ -61,7 +61,7 @@ Use the `/skills` slash command to view and manage available expertise:
|
||||
- `/skills reload`: Refreshes the list of discovered skills from all tiers.
|
||||
|
||||
_Note: `/skills disable` and `/skills enable` default to the `user` scope. Use
|
||||
`--scope project` to manage project-specific settings._
|
||||
`--scope workspace` to manage workspace-specific settings._
|
||||
|
||||
### From the Terminal
|
||||
|
||||
@@ -89,8 +89,8 @@ gemini skills uninstall my-expertise --scope workspace
|
||||
# Enable a skill (globally)
|
||||
gemini skills enable my-expertise
|
||||
|
||||
# Disable a skill. Can use --scope to specify project or user (defaults to project)
|
||||
gemini skills disable my-expertise --scope project
|
||||
# Disable a skill. Can use --scope to specify workspace or user (defaults to workspace)
|
||||
gemini skills disable my-expertise --scope workspace
|
||||
```
|
||||
|
||||
## Creating a Skill
|
||||
@@ -147,7 +147,7 @@ You are an expert code reviewer. When reviewing code, follow this workflow:
|
||||
1. **Analyze**: Review the staged changes or specific files provided. Ensure
|
||||
that the changes are scoped properly and represent minimal changes required
|
||||
to address the issue.
|
||||
2. **Style**: Ensure code follows the project's conventions and idiomatic
|
||||
2. **Style**: Ensure code follows the workspace's conventions and idiomatic
|
||||
patterns as described in the `GEMINI.md` file.
|
||||
3. **Security**: Flag any potential security vulnerabilities.
|
||||
4. **Tests**: Verify that new logic has corresponding test coverage and that
|
||||
|
||||
@@ -84,6 +84,34 @@ describe('skills disable command', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should disable an enabled skill in workspace scope', async () => {
|
||||
const mockSettings = {
|
||||
forScope: vi.fn().mockReturnValue({
|
||||
settings: { skills: { disabled: [] } },
|
||||
path: '/workspace/.gemini/settings.json',
|
||||
}),
|
||||
setValue: vi.fn(),
|
||||
};
|
||||
mockLoadSettings.mockReturnValue(
|
||||
mockSettings as unknown as LoadedSettings,
|
||||
);
|
||||
|
||||
await handleDisable({
|
||||
name: 'skill1',
|
||||
scope: SettingScope.Workspace as LoadableSettingScope,
|
||||
});
|
||||
|
||||
expect(mockSettings.setValue).toHaveBeenCalledWith(
|
||||
SettingScope.Workspace,
|
||||
'skills.disabled',
|
||||
['skill1'],
|
||||
);
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
'Skill "skill1" disabled by adding it to the disabled list in workspace (/workspace/.gemini/settings.json) settings.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should log a message if the skill is already disabled', async () => {
|
||||
const mockSettings = {
|
||||
forScope: vi.fn().mockReturnValue({
|
||||
|
||||
@@ -42,14 +42,16 @@ export const disableCommand: CommandModule = {
|
||||
})
|
||||
.option('scope', {
|
||||
alias: 's',
|
||||
describe: 'The scope to disable the skill in (user or project).',
|
||||
describe: 'The scope to disable the skill in (user or workspace).',
|
||||
type: 'string',
|
||||
default: 'project',
|
||||
choices: ['user', 'project'],
|
||||
default: 'workspace',
|
||||
choices: ['user', 'workspace'],
|
||||
}),
|
||||
handler: async (argv) => {
|
||||
const scope =
|
||||
argv['scope'] === 'project' ? SettingScope.Workspace : SettingScope.User;
|
||||
argv['scope'] === 'workspace'
|
||||
? SettingScope.Workspace
|
||||
: SettingScope.User;
|
||||
await handleDisable({
|
||||
name: argv['name'] as string,
|
||||
scope,
|
||||
|
||||
@@ -64,7 +64,7 @@ describe('skills enable command', () => {
|
||||
path: '/user/settings.json',
|
||||
};
|
||||
}
|
||||
return { settings: {}, path: '/project/settings.json' };
|
||||
return { settings: {}, path: '/workspace/settings.json' };
|
||||
}),
|
||||
setValue: vi.fn(),
|
||||
};
|
||||
@@ -81,7 +81,7 @@ describe('skills enable command', () => {
|
||||
);
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
'Skill "skill1" enabled by removing it from the disabled list in user (/user/settings.json) and project (/project/settings.json) settings.',
|
||||
'Skill "skill1" enabled by removing it from the disabled list in user (/user/settings.json) and workspace (/workspace/settings.json) settings.',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -122,7 +122,7 @@ describe('skills enable command', () => {
|
||||
);
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
'Skill "skill1" enabled by removing it from the disabled list in project (/workspace/settings.json) and user (/user/settings.json) settings.',
|
||||
'Skill "skill1" enabled by removing it from the disabled list in workspace (/workspace/settings.json) and user (/user/settings.json) settings.',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -225,7 +225,7 @@ describe('skillsCommand', () => {
|
||||
expect(context.ui.addItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: MessageType.INFO,
|
||||
text: 'Skill "skill1" disabled by adding it to the disabled list in project (/workspace) settings. Use "/skills reload" for it to take effect.',
|
||||
text: 'Skill "skill1" disabled by adding it to the disabled list in workspace (/workspace) settings. Use "/skills reload" for it to take effect.',
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -253,7 +253,7 @@ describe('skillsCommand', () => {
|
||||
expect(context.ui.addItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: MessageType.INFO,
|
||||
text: 'Skill "skill1" enabled by removing it from the disabled list in project (/workspace) and user (/user/settings.json) settings. Use "/skills reload" for it to take effect.',
|
||||
text: 'Skill "skill1" enabled by removing it from the disabled list in workspace (/workspace) and user (/user/settings.json) settings. Use "/skills reload" for it to take effect.',
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -292,7 +292,7 @@ describe('skillsCommand', () => {
|
||||
expect(context.ui.addItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: MessageType.INFO,
|
||||
text: 'Skill "skill1" enabled by removing it from the disabled list in project (/workspace) and user (/user/settings.json) settings. Use "/skills reload" for it to take effect.',
|
||||
text: 'Skill "skill1" enabled by removing it from the disabled list in workspace (/workspace) and user (/user/settings.json) settings. Use "/skills reload" for it to take effect.',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -47,7 +47,7 @@ export function renderSkillActionFeedback(
|
||||
|
||||
const formatScopeItem = (s: { scope: SettingScope; path: string }) => {
|
||||
const label =
|
||||
s.scope === SettingScope.Workspace ? 'project' : s.scope.toLowerCase();
|
||||
s.scope === SettingScope.Workspace ? 'workspace' : s.scope.toLowerCase();
|
||||
return formatScope(label, s.path);
|
||||
};
|
||||
|
||||
|
||||
@@ -35,9 +35,9 @@ describe('SkillManager', () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should discover skills from extensions, user, and project with precedence', async () => {
|
||||
it('should discover skills from extensions, user, and workspace with precedence', async () => {
|
||||
const userDir = path.join(testRootDir, 'user');
|
||||
const projectDir = path.join(testRootDir, 'project');
|
||||
const projectDir = path.join(testRootDir, 'workspace');
|
||||
await fs.mkdir(path.join(userDir, 'skill-a'), { recursive: true });
|
||||
await fs.mkdir(path.join(projectDir, 'skill-b'), { recursive: true });
|
||||
|
||||
@@ -92,9 +92,9 @@ description: project-desc
|
||||
expect(names).toContain('skill-project');
|
||||
});
|
||||
|
||||
it('should respect precedence: Project > User > Extension', async () => {
|
||||
it('should respect precedence: Workspace > User > Extension', async () => {
|
||||
const userDir = path.join(testRootDir, 'user');
|
||||
const projectDir = path.join(testRootDir, 'project');
|
||||
const projectDir = path.join(testRootDir, 'workspace');
|
||||
await fs.mkdir(path.join(userDir, 'skill'), { recursive: true });
|
||||
await fs.mkdir(path.join(projectDir, 'skill'), { recursive: true });
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@ export class SkillManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Discovers skills from standard user and project locations, as well as extensions.
|
||||
* Precedence: Extensions (lowest) -> User -> Project (highest).
|
||||
* Discovers skills from standard user and workspace locations, as well as extensions.
|
||||
* Precedence: Extensions (lowest) -> User -> Workspace (highest).
|
||||
*/
|
||||
async discoverSkills(
|
||||
storage: Storage,
|
||||
@@ -62,7 +62,7 @@ export class SkillManager {
|
||||
const userSkills = await loadSkillsFromDir(Storage.getUserSkillsDir());
|
||||
this.addSkillsWithPrecedence(userSkills);
|
||||
|
||||
// 4. Project skills (highest precedence)
|
||||
// 4. Workspace skills (highest precedence)
|
||||
const projectSkills = await loadSkillsFromDir(
|
||||
storage.getProjectSkillsDir(),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user