diff --git a/docs/cli/creating-skills.md b/docs/cli/creating-skills.md index 030ea07513..6e2e5eed4e 100644 --- a/docs/cli/creating-skills.md +++ b/docs/cli/creating-skills.md @@ -72,7 +72,8 @@ This skill guides the agent in conducting thorough code reviews. - **Local Changes**: If changes are local... ... ``` -- **`name`**: A unique identifier for the skill. This should match the directory name. +- **`name`**: A unique identifier for the skill. This should match the directory + name. - **`description`**: A description of what the skill does and when Gemini should use it. - **Body**: The Markdown body of the file contains the instructions that guide @@ -81,8 +82,8 @@ This skill guides the agent in conducting thorough code reviews. ## Packaging and Distribution To share a skill or distribute it as a standalone archive, you can package the -skill directory into a `.skill` file. This is essentially a ZIP archive that -the `gemini skills install` command can process. +skill directory into a `.skill` file. This is essentially a ZIP archive that the +`gemini skills install` command can process. ### Using the `skill-creator` toolchain @@ -121,7 +122,8 @@ gemini skills install ./my-skill.skill gemini skills install ./my-skill.skill --scope workspace ``` -After installation, remember to reload your session to pick up the new expertise: +After installation, remember to reload your session to pick up the new +expertise: 1. In an interactive session, run `/skills reload`. 2. Verify the installation with `/skills list`. diff --git a/packages/cli/src/commands/extensions/install.test.ts b/packages/cli/src/commands/extensions/install.test.ts index 8b3f8c5807..8ddd355c11 100644 --- a/packages/cli/src/commands/extensions/install.test.ts +++ b/packages/cli/src/commands/extensions/install.test.ts @@ -15,7 +15,7 @@ import { } from 'vitest'; import { handleInstall, installCommand } from './install.js'; import yargs from 'yargs'; -import * as core from '@google/gemini-cli-core'; +import { debugLogger } from '@google/gemini-cli-core'; import type { Stats } from 'node:fs'; import * as path from 'node:path'; import { promptForSetting } from '../../config/extensions/extensionSettings.js'; @@ -116,12 +116,8 @@ describe('handleInstall', () => { let processSpy: MockInstance; beforeEach(() => { - debugLogSpy = vi - .spyOn(core.debugLogger, 'log') - .mockImplementation(() => {}); - debugErrorSpy = vi - .spyOn(core.debugLogger, 'error') - .mockImplementation(() => {}); + debugLogSpy = vi.spyOn(debugLogger, 'log').mockImplementation(() => {}); + debugErrorSpy = vi.spyOn(debugLogger, 'error').mockImplementation(() => {}); processSpy = vi .spyOn(process, 'exit') .mockImplementation(() => undefined as never); diff --git a/packages/cli/src/config/config.test.ts b/packages/cli/src/config/config.test.ts index 04df366a98..0edaee809a 100644 --- a/packages/cli/src/config/config.test.ts +++ b/packages/cli/src/config/config.test.ts @@ -23,6 +23,11 @@ import { Storage, generalistProfile, type ContextManagementConfig, + PolicyDecision, + createPolicyEngineConfig, + loadServerHierarchicalMemory, + type Config, + TelemetryTarget, } from '@google/gemini-cli-core'; import { loadCliConfig, parseArguments, type CliArgs } from './config.js'; import { @@ -30,7 +35,6 @@ import { type MergedSettings, createTestMergedSettings, } from './settings.js'; -import * as ServerConfig from '@google/gemini-cli-core'; import { isWorkspaceTrusted } from './trustedFolders.js'; import { ExtensionManager } from './extension-manager.js'; @@ -150,9 +154,9 @@ vi.mock('@google/gemini-cli-core', async () => { rules: [], checkers: [], defaultDecision: interactive - ? ServerConfig.PolicyDecision.ASK_USER - : ServerConfig.PolicyDecision.DENY, - approvalMode: approvalMode ?? ServerConfig.ApprovalMode.DEFAULT, + ? PolicyDecision.ASK_USER + : PolicyDecision.DENY, + approvalMode: approvalMode ?? ApprovalMode.DEFAULT, nonInteractive: !interactive, }), ), @@ -979,7 +983,7 @@ describe('Hierarchical Memory Loading (config.ts) - Placeholder Suite', () => { ]); const argv = await parseArguments(createTestMergedSettings()); await loadCliConfig(settings, 'session-id', argv); - expect(ServerConfig.loadServerHierarchicalMemory).toHaveBeenCalledWith( + expect(loadServerHierarchicalMemory).toHaveBeenCalledWith( expect.any(String), [], expect.any(Object), @@ -1009,7 +1013,7 @@ describe('Hierarchical Memory Loading (config.ts) - Placeholder Suite', () => { const argv = await parseArguments(settings); await loadCliConfig(settings, 'session-id', argv); - expect(ServerConfig.loadServerHierarchicalMemory).toHaveBeenCalledWith( + expect(loadServerHierarchicalMemory).toHaveBeenCalledWith( expect.any(String), [includeDir], expect.any(Object), @@ -1038,7 +1042,7 @@ describe('Hierarchical Memory Loading (config.ts) - Placeholder Suite', () => { const argv = await parseArguments(settings); await loadCliConfig(settings, 'session-id', argv); - expect(ServerConfig.loadServerHierarchicalMemory).toHaveBeenCalledWith( + expect(loadServerHierarchicalMemory).toHaveBeenCalledWith( expect.any(String), [], expect.any(Object), @@ -2739,7 +2743,7 @@ describe('loadCliConfig approval mode', () => { 'test-session', argv, ); - expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); + expect(config.getApprovalMode()).toBe(ApprovalMode.DEFAULT); }); it('should set YOLO approval mode when --yolo flag is used', async () => { @@ -2750,7 +2754,7 @@ describe('loadCliConfig approval mode', () => { 'test-session', argv, ); - expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.YOLO); + expect(config.getApprovalMode()).toBe(ApprovalMode.YOLO); }); it('should set YOLO approval mode when -y flag is used', async () => { @@ -2761,7 +2765,7 @@ describe('loadCliConfig approval mode', () => { 'test-session', argv, ); - expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.YOLO); + expect(config.getApprovalMode()).toBe(ApprovalMode.YOLO); }); it('should set DEFAULT approval mode when --approval-mode=default', async () => { @@ -2772,7 +2776,7 @@ describe('loadCliConfig approval mode', () => { 'test-session', argv, ); - expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); + expect(config.getApprovalMode()).toBe(ApprovalMode.DEFAULT); }); it('should set AUTO_EDIT approval mode when --approval-mode=auto_edit', async () => { @@ -2783,7 +2787,7 @@ describe('loadCliConfig approval mode', () => { 'test-session', argv, ); - expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.AUTO_EDIT); + expect(config.getApprovalMode()).toBe(ApprovalMode.AUTO_EDIT); }); it('should set YOLO approval mode when --approval-mode=yolo', async () => { @@ -2794,7 +2798,7 @@ describe('loadCliConfig approval mode', () => { 'test-session', argv, ); - expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.YOLO); + expect(config.getApprovalMode()).toBe(ApprovalMode.YOLO); }); it('should prioritize --approval-mode over --yolo when both would be valid (but validation prevents this)', async () => { @@ -2809,7 +2813,7 @@ describe('loadCliConfig approval mode', () => { 'test-session', argv, ); - expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); + expect(config.getApprovalMode()).toBe(ApprovalMode.DEFAULT); }); it('should fall back to --yolo behavior when --approval-mode is not set', async () => { @@ -2820,7 +2824,7 @@ describe('loadCliConfig approval mode', () => { 'test-session', argv, ); - expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.YOLO); + expect(config.getApprovalMode()).toBe(ApprovalMode.YOLO); }); it('should set Plan approval mode when --approval-mode=plan is used and plan is enabled', async () => { @@ -2832,7 +2836,7 @@ describe('loadCliConfig approval mode', () => { }, }); const config = await loadCliConfig(settings, 'test-session', argv); - expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.PLAN); + expect(config.getApprovalMode()).toBe(ApprovalMode.PLAN); }); it('should ignore "yolo" in settings.tools.approvalMode and fall back to DEFAULT', async () => { @@ -2845,7 +2849,7 @@ describe('loadCliConfig approval mode', () => { }); const argv = await parseArguments(settings); const config = await loadCliConfig(settings, 'test-session', argv); - expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); + expect(config.getApprovalMode()).toBe(ApprovalMode.DEFAULT); }); it('should throw error when --approval-mode=plan is used but plan is disabled', async () => { @@ -2902,7 +2906,7 @@ describe('loadCliConfig approval mode', () => { 'test-session', argv, ); - expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); + expect(config.getApprovalMode()).toBe(ApprovalMode.DEFAULT); }); it('should override --approval-mode=auto_edit to DEFAULT', async () => { @@ -2913,7 +2917,7 @@ describe('loadCliConfig approval mode', () => { 'test-session', argv, ); - expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); + expect(config.getApprovalMode()).toBe(ApprovalMode.DEFAULT); }); it('should override --yolo flag to DEFAULT', async () => { @@ -2924,7 +2928,7 @@ describe('loadCliConfig approval mode', () => { 'test-session', argv, ); - expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); + expect(config.getApprovalMode()).toBe(ApprovalMode.DEFAULT); }); it('should remain DEFAULT when --approval-mode=default', async () => { @@ -2935,7 +2939,7 @@ describe('loadCliConfig approval mode', () => { 'test-session', argv, ); - expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); + expect(config.getApprovalMode()).toBe(ApprovalMode.DEFAULT); }); }); @@ -2947,9 +2951,7 @@ describe('loadCliConfig approval mode', () => { }); const argv = await parseArguments(settings); const config = await loadCliConfig(settings, 'test-session', argv); - expect(config.getApprovalMode()).toBe( - ServerConfig.ApprovalMode.AUTO_EDIT, - ); + expect(config.getApprovalMode()).toBe(ApprovalMode.AUTO_EDIT); }); it('should prioritize --approval-mode flag over settings', async () => { @@ -2959,9 +2961,7 @@ describe('loadCliConfig approval mode', () => { }); const argv = await parseArguments(settings); const config = await loadCliConfig(settings, 'test-session', argv); - expect(config.getApprovalMode()).toBe( - ServerConfig.ApprovalMode.AUTO_EDIT, - ); + expect(config.getApprovalMode()).toBe(ApprovalMode.AUTO_EDIT); }); it('should prioritize --yolo flag over settings', async () => { @@ -2971,7 +2971,7 @@ describe('loadCliConfig approval mode', () => { }); const argv = await parseArguments(settings); const config = await loadCliConfig(settings, 'test-session', argv); - expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.YOLO); + expect(config.getApprovalMode()).toBe(ApprovalMode.YOLO); }); it('should respect plan mode from settings when plan is enabled', async () => { @@ -2984,7 +2984,7 @@ describe('loadCliConfig approval mode', () => { }); const argv = await parseArguments(settings); const config = await loadCliConfig(settings, 'test-session', argv); - expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.PLAN); + expect(config.getApprovalMode()).toBe(ApprovalMode.PLAN); }); it('should fall back to default if plan mode is in settings but disabled', async () => { @@ -3084,7 +3084,7 @@ describe('loadCliConfig fileFiltering', () => { >; const testCases: Array<{ property: keyof FileFilteringSettings; - getter: (config: ServerConfig.Config) => boolean; + getter: (config: Config) => boolean; value: boolean; }> = [ { @@ -3328,7 +3328,7 @@ describe('Telemetry configuration via environment variables', () => { process.argv = ['node', 'script.js']; const argv = await parseArguments(createTestMergedSettings()); const settings = createTestMergedSettings({ - telemetry: { target: ServerConfig.TelemetryTarget.LOCAL }, + telemetry: { target: TelemetryTarget.LOCAL }, }); const config = await loadCliConfig(settings, 'test-session', argv); expect(config.getTelemetryTarget()).toBe('gcp'); @@ -3339,7 +3339,7 @@ describe('Telemetry configuration via environment variables', () => { process.argv = ['node', 'script.js']; const argv = await parseArguments(createTestMergedSettings()); const settings = createTestMergedSettings({ - telemetry: { target: ServerConfig.TelemetryTarget.GCP }, + telemetry: { target: TelemetryTarget.GCP }, }); await expect(loadCliConfig(settings, 'test-session', argv)).rejects.toThrow( /Invalid telemetry configuration: .*Invalid telemetry target/i, @@ -3417,7 +3417,7 @@ describe('Telemetry configuration via environment variables', () => { process.argv = ['node', 'script.js']; const argv = await parseArguments(createTestMergedSettings()); const settings = createTestMergedSettings({ - telemetry: { target: ServerConfig.TelemetryTarget.LOCAL }, + telemetry: { target: TelemetryTarget.LOCAL }, }); const config = await loadCliConfig(settings, 'test-session', argv); expect(config.getTelemetryTarget()).toBe('local'); @@ -3541,7 +3541,7 @@ describe('Policy Engine Integration in loadCliConfig', () => { await loadCliConfig(settings, 'test-session', argv); - expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith( + expect(createPolicyEngineConfig).toHaveBeenCalledWith( expect.objectContaining({ tools: expect.objectContaining({ allowed: expect.arrayContaining(['cli-tool']), @@ -3564,7 +3564,7 @@ describe('Policy Engine Integration in loadCliConfig', () => { await loadCliConfig(settings, 'test-session', argv); // In non-interactive mode, only ask_user is excluded by default - expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith( + expect(createPolicyEngineConfig).toHaveBeenCalledWith( expect.objectContaining({ tools: expect.objectContaining({ exclude: expect.arrayContaining([ASK_USER_TOOL_NAME]), @@ -3588,7 +3588,7 @@ describe('Policy Engine Integration in loadCliConfig', () => { await loadCliConfig(settings, 'test-session', argv); - expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith( + expect(createPolicyEngineConfig).toHaveBeenCalledWith( expect.objectContaining({ policyPaths: [ path.normalize('/path/to/policy1.toml'), diff --git a/packages/cli/src/config/workspace-policy-cli.test.ts b/packages/cli/src/config/workspace-policy-cli.test.ts index bd9bcd0105..39868b0c3d 100644 --- a/packages/cli/src/config/workspace-policy-cli.test.ts +++ b/packages/cli/src/config/workspace-policy-cli.test.ts @@ -6,9 +6,13 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import * as path from 'node:path'; +import { + isHeadlessMode, + Storage, + createPolicyEngineConfig, +} from '@google/gemini-cli-core'; import { loadCliConfig, type CliArgs } from './config.js'; import { createTestMergedSettings } from './settings.js'; -import * as ServerConfig from '@google/gemini-cli-core'; import { isWorkspaceTrusted } from './trustedFolders.js'; import * as Policy from './policy.js'; @@ -21,9 +25,9 @@ const mockCheckIntegrity = vi.fn(); const mockAcceptIntegrity = vi.fn(); vi.mock('@google/gemini-cli-core', async () => { - const actual = await vi.importActual( - '@google/gemini-cli-core', - ); + const actual = await vi.importActual< + typeof import('@google/gemini-cli-core') + >('@google/gemini-cli-core'); return { ...actual, loadServerHierarchicalMemory: vi.fn().mockResolvedValue({ @@ -61,11 +65,11 @@ describe('Workspace-Level Policy CLI Integration', () => { hash: 'test-hash', fileCount: 1, }); - vi.mocked(ServerConfig.isHeadlessMode).mockReturnValue(false); + vi.mocked(isHeadlessMode).mockReturnValue(false); }); it('should have getWorkspacePoliciesDir on Storage class', () => { - const storage = new ServerConfig.Storage(MOCK_CWD); + const storage = new Storage(MOCK_CWD); expect(storage.getWorkspacePoliciesDir).toBeDefined(); expect(typeof storage.getWorkspacePoliciesDir).toBe('function'); }); @@ -81,7 +85,7 @@ describe('Workspace-Level Policy CLI Integration', () => { await loadCliConfig(settings, 'test-session', argv, { cwd: MOCK_CWD }); - expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith( + expect(createPolicyEngineConfig).toHaveBeenCalledWith( expect.objectContaining({ workspacePoliciesDir: expect.stringContaining( path.join('.gemini', 'policies'), @@ -104,7 +108,7 @@ describe('Workspace-Level Policy CLI Integration', () => { await loadCliConfig(settings, 'test-session', argv, { cwd: MOCK_CWD }); - expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith( + expect(createPolicyEngineConfig).toHaveBeenCalledWith( expect.objectContaining({ workspacePoliciesDir: undefined, }), @@ -130,7 +134,7 @@ describe('Workspace-Level Policy CLI Integration', () => { await loadCliConfig(settings, 'test-session', argv, { cwd: MOCK_CWD }); - expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith( + expect(createPolicyEngineConfig).toHaveBeenCalledWith( expect.objectContaining({ workspacePoliciesDir: undefined, }), @@ -150,7 +154,7 @@ describe('Workspace-Level Policy CLI Integration', () => { hash: 'new-hash', fileCount: 1, }); - vi.mocked(ServerConfig.isHeadlessMode).mockReturnValue(true); // Non-interactive + vi.mocked(isHeadlessMode).mockReturnValue(true); // Non-interactive const settings = createTestMergedSettings(); const argv = { prompt: 'do something' } as unknown as CliArgs; @@ -162,7 +166,7 @@ describe('Workspace-Level Policy CLI Integration', () => { MOCK_CWD, 'new-hash', ); - expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith( + expect(createPolicyEngineConfig).toHaveBeenCalledWith( expect.objectContaining({ workspacePoliciesDir: expect.stringContaining( path.join('.gemini', 'policies'), @@ -184,7 +188,7 @@ describe('Workspace-Level Policy CLI Integration', () => { hash: 'new-hash', fileCount: 1, }); - vi.mocked(ServerConfig.isHeadlessMode).mockReturnValue(false); // Interactive + vi.mocked(isHeadlessMode).mockReturnValue(false); // Interactive const settings = createTestMergedSettings(); const argv = { @@ -202,7 +206,7 @@ describe('Workspace-Level Policy CLI Integration', () => { MOCK_CWD, 'new-hash', ); - expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith( + expect(createPolicyEngineConfig).toHaveBeenCalledWith( expect.objectContaining({ workspacePoliciesDir: expect.stringContaining( path.join('.gemini', 'policies'), @@ -224,7 +228,7 @@ describe('Workspace-Level Policy CLI Integration', () => { hash: 'new-hash', fileCount: 5, }); - vi.mocked(ServerConfig.isHeadlessMode).mockReturnValue(false); // Interactive + vi.mocked(isHeadlessMode).mockReturnValue(false); // Interactive const settings = createTestMergedSettings(); const argv = { query: 'test' } as unknown as CliArgs; @@ -240,7 +244,7 @@ describe('Workspace-Level Policy CLI Integration', () => { 'new-hash', ); - expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith( + expect(createPolicyEngineConfig).toHaveBeenCalledWith( expect.objectContaining({ workspacePoliciesDir: expect.stringContaining( path.join('.gemini', 'policies'), @@ -267,7 +271,7 @@ describe('Workspace-Level Policy CLI Integration', () => { hash: 'new-hash', fileCount: 1, }); - vi.mocked(ServerConfig.isHeadlessMode).mockReturnValue(false); // Interactive + vi.mocked(isHeadlessMode).mockReturnValue(false); // Interactive const settings = createTestMergedSettings(); const argv = { @@ -285,7 +289,7 @@ describe('Workspace-Level Policy CLI Integration', () => { policyDir: expect.stringContaining(path.join('.gemini', 'policies')), newHash: 'new-hash', }); - expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith( + expect(createPolicyEngineConfig).toHaveBeenCalledWith( expect.objectContaining({ workspacePoliciesDir: undefined, }), diff --git a/packages/cli/src/utils/worktreeSetup.test.ts b/packages/cli/src/utils/worktreeSetup.test.ts index e1bd201a8b..9b6087628a 100644 --- a/packages/cli/src/utils/worktreeSetup.test.ts +++ b/packages/cli/src/utils/worktreeSetup.test.ts @@ -6,7 +6,11 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { setupWorktree } from './worktreeSetup.js'; -import * as coreFunctions from '@google/gemini-cli-core'; +import { + getProjectRootForWorktree, + createWorktreeService, + writeToStderr, +} from '@google/gemini-cli-core'; // Mock dependencies vi.mock('@google/gemini-cli-core', async (importOriginal) => { @@ -47,12 +51,8 @@ describe('setupWorktree', () => { }); // Mock successful execution of core utilities - vi.mocked(coreFunctions.getProjectRootForWorktree).mockResolvedValue( - '/mock/project', - ); - vi.mocked(coreFunctions.createWorktreeService).mockResolvedValue( - mockService as never, - ); + vi.mocked(getProjectRootForWorktree).mockResolvedValue('/mock/project'); + vi.mocked(createWorktreeService).mockResolvedValue(mockService as never); mockService.setup.mockResolvedValue({ name: 'my-feature', path: '/mock/project/.gemini/worktrees/my-feature', @@ -69,12 +69,8 @@ describe('setupWorktree', () => { it('should create and switch to a new worktree', async () => { await setupWorktree('my-feature'); - expect(coreFunctions.getProjectRootForWorktree).toHaveBeenCalledWith( - '/mock/project', - ); - expect(coreFunctions.createWorktreeService).toHaveBeenCalledWith( - '/mock/project', - ); + expect(getProjectRootForWorktree).toHaveBeenCalledWith('/mock/project'); + expect(createWorktreeService).toHaveBeenCalledWith('/mock/project'); expect(mockService.setup).toHaveBeenCalledWith('my-feature'); expect(process.chdir).toHaveBeenCalledWith( '/mock/project/.gemini/worktrees/my-feature', @@ -99,7 +95,7 @@ describe('setupWorktree', () => { await setupWorktree('my-feature'); - expect(coreFunctions.createWorktreeService).not.toHaveBeenCalled(); + expect(createWorktreeService).not.toHaveBeenCalled(); expect(process.chdir).not.toHaveBeenCalled(); }); @@ -112,7 +108,7 @@ describe('setupWorktree', () => { await expect(setupWorktree('my-feature')).rejects.toThrow('PROCESS_EXIT'); - expect(coreFunctions.writeToStderr).toHaveBeenCalledWith( + expect(writeToStderr).toHaveBeenCalledWith( expect.stringContaining( 'Failed to create or switch to worktree: Git failure', ),