diff --git a/docs/cli/creating-skills.md b/docs/cli/creating-skills.md index 71f7e6df8a..6e2e5eed4e 100644 --- a/docs/cli/creating-skills.md +++ b/docs/cli/creating-skills.md @@ -78,3 +78,52 @@ This skill guides the agent in conducting thorough code reviews. use it. - **Body**: The Markdown body of the file contains the instructions that guide the agent's behavior when the skill is active. + +## 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. + +### Using the `skill-creator` toolchain + +The built-in `skill-creator` skill includes a packaging script that validates +your skill (checking for missing frontmatter, unresolved TODOs, etc.) before +creating the archive. + +To package a skill: + +1. **Activate** the `skill-creator` skill in a Gemini session. +2. **Ask** the agent to "package my skill located at `./path/to/skill`". + +The agent will run the necessary validation and create a `.skill` file in your +specified output directory. + +### Manual Packaging + +If you prefer to package manually, you can simply create a ZIP archive of the +skill's root directory (ensuring `SKILL.md` is at the top level) and rename the +extension to `.skill`. + +```bash +# Example: Package 'my-skill' directory into 'my-skill.skill' +(cd my-skill/ && zip -r ../my-skill.skill .) +``` + +## Installing a Packaged Skill + +Once you have a `.skill` file, you can install it using the `gemini` CLI. + +```bash +# Install to the user scope (global) +gemini skills install ./my-skill.skill + +# Install to the workspace scope (local repository) +gemini skills install ./my-skill.skill --scope workspace +``` + +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/docs/cli/skills.md b/docs/cli/skills.md index 73e5eb66eb..4ac83e7c1e 100644 --- a/docs/cli/skills.md +++ b/docs/cli/skills.md @@ -106,6 +106,18 @@ gemini skills enable my-expertise gemini skills disable my-expertise --scope workspace ``` +### Activation and Reloading + +After installing or linking a new skill, it is not automatically available in +currently active sessions. To pick up the new expertise: + +1. **In an interactive session**: Run the `/skills reload` slash command. This + refreshes the list of discovered skills from all tiers and scopes. +2. **Verify**: Run `/skills list` to confirm the new skill is discovered and + enabled. +3. **Automatic Discovery**: New sessions will automatically discover all + available skills during startup. + ## How it Works 1. **Discovery**: At the start of a session, Gemini CLI scans the discovery 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 180f461749..98acef09da 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, }), ), @@ -986,7 +990,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), @@ -1016,7 +1020,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), @@ -1045,7 +1049,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), @@ -2746,7 +2750,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 () => { @@ -2757,7 +2761,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 () => { @@ -2768,7 +2772,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 () => { @@ -2779,7 +2783,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 () => { @@ -2790,7 +2794,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 () => { @@ -2801,7 +2805,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 () => { @@ -2816,7 +2820,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 () => { @@ -2827,7 +2831,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 () => { @@ -2839,7 +2843,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 () => { @@ -2852,7 +2856,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 () => { @@ -2909,7 +2913,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 () => { @@ -2920,7 +2924,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 () => { @@ -2931,7 +2935,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 () => { @@ -2942,7 +2946,7 @@ describe('loadCliConfig approval mode', () => { 'test-session', argv, ); - expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); + expect(config.getApprovalMode()).toBe(ApprovalMode.DEFAULT); }); }); @@ -2954,9 +2958,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 () => { @@ -2966,9 +2968,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 () => { @@ -2978,7 +2978,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 () => { @@ -2991,7 +2991,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 () => { @@ -3097,7 +3097,7 @@ describe('loadCliConfig fileFiltering', () => { >; const testCases: Array<{ property: keyof FileFilteringSettings; - getter: (config: ServerConfig.Config) => boolean; + getter: (config: Config) => boolean; value: boolean; }> = [ { @@ -3341,7 +3341,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'); @@ -3352,7 +3352,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, @@ -3430,7 +3430,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'); @@ -3554,7 +3554,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']), @@ -3577,7 +3577,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]), @@ -3601,7 +3601,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', ),