/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { getCoreSystemPrompt } from './prompts.js';
import { resolvePathFromEnv } from '../prompts/utils.js';
import { isGitRepository } from '../utils/gitUtils.js';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { CodebaseInvestigatorAgent } from '../agents/codebase-investigator.js';
import { GEMINI_DIR } from '../utils/paths.js';
import { debugLogger } from '../utils/debugLogger.js';
import { PREVIEW_GEMINI_MODEL, PREVIEW_GEMINI_FLASH_MODEL, DEFAULT_GEMINI_MODEL_AUTO, DEFAULT_GEMINI_MODEL, DEFAULT_GEMINI_FLASH_LITE_MODEL, } from '../config/models.js';
import { ApprovalMode } from '../policy/types.js';
import { DiscoveredMCPTool } from '../tools/mcp-tool.js';
// Mock tool names if they are dynamically generated or complex
vi.mock('../tools/ls', () => ({ LSTool: { Name: 'list_directory' } }));
vi.mock('../tools/edit', () => ({ EditTool: { Name: 'replace' } }));
vi.mock('../tools/glob', () => ({ GlobTool: { Name: 'glob' } }));
vi.mock('../tools/grep', () => ({ GrepTool: { Name: 'grep_search' } }));
vi.mock('../tools/read-file', () => ({ ReadFileTool: { Name: 'read_file' } }));
vi.mock('../tools/read-many-files', () => ({
    ReadManyFilesTool: { Name: 'read_many_files' },
}));
vi.mock('../tools/shell', () => ({
    ShellTool: class {
        static Name = 'run_shell_command';
        name = 'run_shell_command';
    },
}));
vi.mock('../tools/write-file', () => ({
    WriteFileTool: { Name: 'write_file' },
}));
vi.mock('../agents/codebase-investigator.js', () => ({
    CodebaseInvestigatorAgent: { name: 'codebase_investigator' },
}));
vi.mock('../utils/gitUtils', () => ({
    isGitRepository: vi.fn().mockReturnValue(false),
}));
vi.mock('node:fs');
vi.mock('../config/models.js', async (importOriginal) => {
    const actual = await importOriginal();
    return {
        ...actual,
    };
});
describe('Core System Prompt (prompts.ts)', () => {
    const mockPlatform = (platform) => {
        vi.stubGlobal('process', Object.create(process, {
            platform: {
                get: () => platform,
            },
        }));
    };
    let mockConfig;
    beforeEach(() => {
        vi.resetAllMocks();
        // Stub process.platform to 'linux' by default for deterministic snapshots across OSes
        mockPlatform('linux');
        vi.stubEnv('SANDBOX', undefined);
        vi.stubEnv('GEMINI_SYSTEM_MD', undefined);
        vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', undefined);
        const mockRegistry = {
            getAllToolNames: vi.fn().mockReturnValue(['grep_search', 'glob']),
            getAllTools: vi.fn().mockReturnValue([]),
        };
        mockConfig = {
            getToolRegistry: vi.fn().mockReturnValue(mockRegistry),
            getEnableShellOutputEfficiency: vi.fn().mockReturnValue(true),
            storage: {
                getProjectTempDir: vi.fn().mockReturnValue('/tmp/project-temp'),
                getPlansDir: vi.fn().mockReturnValue('/tmp/project-temp/plans'),
            },
            isInteractive: vi.fn().mockReturnValue(true),
            isInteractiveShellEnabled: vi.fn().mockReturnValue(true),
            isTopicUpdateNarrationEnabled: vi.fn().mockReturnValue(false),
            isAgentsEnabled: vi.fn().mockReturnValue(false),
            getPreviewFeatures: vi.fn().mockReturnValue(true),
            getModel: vi.fn().mockReturnValue(DEFAULT_GEMINI_MODEL_AUTO),
            getActiveModel: vi.fn().mockReturnValue(DEFAULT_GEMINI_MODEL),
            getMessageBus: vi.fn(),
            getAgentRegistry: vi.fn().mockReturnValue({
                getDirectoryContext: vi.fn().mockReturnValue('Mock Agent Directory'),
                getAllDefinitions: vi.fn().mockReturnValue([
                    {
                        name: 'mock-agent',
                        description: 'Mock Agent Description',
                    },
                ]),
            }),
            getSkillManager: vi.fn().mockReturnValue({
                getSkills: vi.fn().mockReturnValue([]),
            }),
            getApprovalMode: vi.fn().mockReturnValue(ApprovalMode.DEFAULT),
            getApprovedPlanPath: vi.fn().mockReturnValue(undefined),
            isTrackerEnabled: vi.fn().mockReturnValue(false),
            get config() {
                return this;
            },
            get toolRegistry() {
                return mockRegistry;
            },
        };
    });
    afterEach(() => {
        vi.unstubAllGlobals();
    });
    it('should include available_skills when provided in config', () => {
        const skills = [
            {
                name: 'test-skill',
                description: 'A test skill description',
                location: '/path/to/test-skill/SKILL.md',
                body: 'Skill content',
            },
        ];
        vi.mocked(mockConfig.getSkillManager().getSkills).mockReturnValue(skills);
        const prompt = getCoreSystemPrompt(mockConfig);
        expect(prompt).toContain('# Available Agent Skills');
        expect(prompt).toContain("To activate a skill and receive its detailed instructions, you can call the `activate_skill` tool with the skill's name.");
        expect(prompt).toContain('Skill Guidance');
        expect(prompt).toContain('<available_skills>');
        expect(prompt).toContain('<skill>');
        expect(prompt).toContain('<name>test-skill</name>');
        expect(prompt).toContain('<description>A test skill description</description>');
        expect(prompt).toContain('<location>/path/to/test-skill/SKILL.md</location>');
        expect(prompt).toContain('</skill>');
        expect(prompt).toContain('</available_skills>');
        expect(prompt).toMatchSnapshot();
    });
    it('should include available_skills with updated verbiage for preview models', () => {
        vi.mocked(mockConfig.getActiveModel).mockReturnValue(PREVIEW_GEMINI_MODEL);
        const skills = [
            {
                name: 'test-skill',
                description: 'A test skill description',
                location: '/path/to/test-skill/SKILL.md',
                body: 'Skill content',
            },
        ];
        vi.mocked(mockConfig.getSkillManager().getSkills).mockReturnValue(skills);
        const prompt = getCoreSystemPrompt(mockConfig);
        expect(prompt).toContain('# Available Agent Skills');
        expect(prompt).toContain("To activate a skill and receive its detailed instructions, call the `activate_skill` tool with the skill's name.");
        expect(prompt).toMatchSnapshot();
    });
    it('should NOT include skill guidance or available_skills when NO skills are provided', () => {
        vi.mocked(mockConfig.getSkillManager().getSkills).mockReturnValue([]);
        const prompt = getCoreSystemPrompt(mockConfig);
        expect(prompt).not.toContain('# Available Agent Skills');
        expect(prompt).not.toContain('Skill Guidance');
        expect(prompt).not.toContain('activate_skill');
    });
    it('should include sub-agents in XML for preview models', () => {
        vi.mocked(mockConfig.getActiveModel).mockReturnValue(PREVIEW_GEMINI_MODEL);
        const agents = [
            {
                name: 'test-agent',
                displayName: 'Test Agent',
                description: 'A test agent description',
            },
        ];
        vi.mocked(mockConfig.getAgentRegistry().getAllDefinitions).mockReturnValue(agents);
        const prompt = getCoreSystemPrompt(mockConfig);
        expect(prompt).toContain('# Available Sub-Agents');
        expect(prompt).toContain('<available_subagents>');
        expect(prompt).toContain('<subagent>');
        expect(prompt).toContain('<name>test-agent</name>');
        expect(prompt).toContain('<description>A test agent description</description>');
        expect(prompt).toContain('</subagent>');
        expect(prompt).toContain('</available_subagents>');
        expect(prompt).toMatchSnapshot();
    });
    it('should use legacy system prompt for non-preview model', () => {
        vi.mocked(mockConfig.getActiveModel).mockReturnValue(DEFAULT_GEMINI_FLASH_LITE_MODEL);
        const prompt = getCoreSystemPrompt(mockConfig);
        expect(prompt).toContain('You are an interactive CLI agent specializing in software engineering tasks.');
        expect(prompt).not.toContain('No sub-agents are currently available.');
        expect(prompt).toContain('# Core Mandates');
        expect(prompt).toContain('- **Conventions:**');
        expect(prompt).toContain('- **User Hints:**');
        expect(prompt).toContain('# Outside of Sandbox');
        expect(prompt).toContain('# Final Reminder');
        expect(prompt).toMatchSnapshot();
    });
    it('should include the TASK MANAGEMENT PROTOCOL when task tracker is enabled', () => {
        vi.mocked(mockConfig.getActiveModel).mockReturnValue(PREVIEW_GEMINI_MODEL);
        vi.mocked(mockConfig.isTrackerEnabled).mockReturnValue(true);
        const prompt = getCoreSystemPrompt(mockConfig);
        expect(prompt).toContain('# TASK MANAGEMENT PROTOCOL');
        expect(prompt).toContain('**PLAN MODE INTEGRATION**: If an approved plan exists, you MUST use the `tracker_create_task` tool to decompose it into discrete tasks before writing any code');
        expect(prompt).toMatchSnapshot();
    });
    it('should use chatty system prompt for preview model', () => {
        vi.mocked(mockConfig.getActiveModel).mockReturnValue(PREVIEW_GEMINI_MODEL);
        const prompt = getCoreSystemPrompt(mockConfig);
        expect(prompt).toContain('You are Gemini CLI, an interactive CLI agent'); // Check for core content
        expect(prompt).toContain('- **User Hints:**');
        expect(prompt).toContain('No Chitchat:');
        expect(prompt).toMatchSnapshot();
    });
    it('should use chatty system prompt for preview flash model', () => {
        vi.mocked(mockConfig.getActiveModel).mockReturnValue(PREVIEW_GEMINI_FLASH_MODEL);
        const prompt = getCoreSystemPrompt(mockConfig);
        expect(prompt).toContain('You are Gemini CLI, an interactive CLI agent'); // Check for core content
        expect(prompt).toContain('No Chitchat:');
        expect(prompt).toMatchSnapshot();
    });
    it('should include mandate to distinguish between Directives and Inquiries', () => {
        vi.mocked(mockConfig.getActiveModel).mockReturnValue(PREVIEW_GEMINI_MODEL);
        const prompt = getCoreSystemPrompt(mockConfig);
        expect(prompt).toContain('Distinguish between **Directives**');
        expect(prompt).toContain('and **Inquiries**');
        expect(prompt).toContain('Assume all requests are Inquiries unless they contain an explicit instruction to perform a task.');
        expect(prompt).toMatchSnapshot();
    });
    it.each([
        ['empty string', ''],
        ['whitespace only', '   \n  \t '],
    ])('should return the base prompt when userMemory is %s', (_, userMemory) => {
        vi.stubEnv('SANDBOX', undefined);
        vi.mocked(mockConfig.getActiveModel).mockReturnValue(PREVIEW_GEMINI_MODEL);
        const prompt = getCoreSystemPrompt(mockConfig, userMemory);
        expect(prompt).not.toContain('---\n\n'); // Separator should not be present
        expect(prompt).toContain('You are Gemini CLI, an interactive CLI agent'); // Check for core content
        expect(prompt).toContain('No Chitchat:');
        expect(prompt).toMatchSnapshot(); // Use snapshot for base prompt structure
    });
    it('should append userMemory with separator when provided', () => {
        vi.stubEnv('SANDBOX', undefined);
        vi.mocked(mockConfig.getActiveModel).mockReturnValue(PREVIEW_GEMINI_MODEL);
        const memory = 'This is custom user memory.\nBe extra polite.';
        const prompt = getCoreSystemPrompt(mockConfig, memory);
        expect(prompt).toContain('# Contextual Instructions (GEMINI.md)');
        expect(prompt).toContain('<loaded_context>');
        expect(prompt).toContain(memory);
        expect(prompt).toContain('You are Gemini CLI, an interactive CLI agent'); // Ensure base prompt follows
        expect(prompt).toMatchSnapshot(); // Snapshot the combined prompt
    });
    it('should render hierarchical memory with XML tags', () => {
        vi.stubEnv('SANDBOX', undefined);
        const memory = {
            global: 'global context',
            extension: 'extension context',
            project: 'project context',
        };
        const prompt = getCoreSystemPrompt(mockConfig, memory);
        expect(prompt).toContain('<global_context>\nglobal context\n</global_context>');
        expect(prompt).toContain('<extension_context>\nextension context\n</extension_context>');
        expect(prompt).toContain('<project_context>\nproject context\n</project_context>');
        expect(prompt).toMatchSnapshot();
        // Should also include conflict resolution rules when hierarchical memory is present
        expect(prompt).toContain('Conflict Resolution:');
    });
    it('should match snapshot on Windows', () => {
        mockPlatform('win32');
        vi.stubEnv('SANDBOX', undefined);
        const prompt = getCoreSystemPrompt(mockConfig);
        expect(prompt).toMatchSnapshot();
    });
    it.each([
        ['true', '# Sandbox', ['# macOS Seatbelt', '# Outside of Sandbox']],
        ['sandbox-exec', '# macOS Seatbelt', ['# Sandbox', '# Outside of Sandbox']],
        [
            undefined,
            'You are Gemini CLI, an interactive CLI agent',
            ['# Sandbox', '# macOS Seatbelt'],
        ],
    ])('should include correct sandbox instructions for SANDBOX=%s', (sandboxValue, expectedContains, expectedNotContains) => {
        vi.stubEnv('SANDBOX', sandboxValue);
        vi.mocked(mockConfig.getActiveModel).mockReturnValue(PREVIEW_GEMINI_MODEL);
        const prompt = getCoreSystemPrompt(mockConfig);
        expect(prompt).toContain(expectedContains);
        // modern snippets should NOT contain outside
        expect(prompt).not.toContain('# Outside of Sandbox');
        expectedNotContains.forEach((text) => expect(prompt).not.toContain(text));
        expect(prompt).toMatchSnapshot();
    });
    it.each([
        [true, true],
        [false, false],
    ])('should handle git instructions when isGitRepository=%s', (isGitRepo, shouldContainGit) => {
        vi.stubEnv('SANDBOX', undefined);
        vi.mocked(isGitRepository).mockReturnValue(isGitRepo);
        const prompt = getCoreSystemPrompt(mockConfig);
        shouldContainGit
            ? expect(prompt).toContain('# Git Repository')
            : expect(prompt).not.toContain('# Git Repository');
        expect(prompt).toMatchSnapshot();
    });
    it('should return the interactive avoidance prompt when in non-interactive mode', () => {
        vi.stubEnv('SANDBOX', undefined);
        mockConfig.isInteractive = vi.fn().mockReturnValue(false);
        const prompt = getCoreSystemPrompt(mockConfig, '');
        expect(prompt).toContain('**Interactive Commands:**'); // Check for interactive prompt
        expect(prompt).toMatchSnapshot(); // Use snapshot for base prompt structure
    });
    it('should redact grep and glob from the system prompt when they are disabled', () => {
        vi.mocked(mockConfig.getActiveModel).mockReturnValue(PREVIEW_GEMINI_MODEL);
        vi.mocked(mockConfig.toolRegistry.getAllToolNames).mockReturnValue([]);
        const prompt = getCoreSystemPrompt(mockConfig);
        expect(prompt).not.toContain('`grep_search`');
        expect(prompt).not.toContain('`glob`');
        expect(prompt).toContain('Use search tools extensively to understand file structures, existing code patterns, and conventions.');
    });
    it.each([
        [[CodebaseInvestigatorAgent.name, 'grep_search', 'glob'], true],
        [['grep_search', 'glob'], false],
    ])('should handle CodebaseInvestigator with tools=%s', (toolNames, expectCodebaseInvestigator) => {
        const mockToolRegistry = {
            getAllToolNames: vi.fn().mockReturnValue(toolNames),
        };
        const testConfig = {
            getToolRegistry: vi.fn().mockReturnValue(mockToolRegistry),
            getEnableShellOutputEfficiency: vi.fn().mockReturnValue(true),
            storage: {
                getProjectTempDir: vi.fn().mockReturnValue('/tmp/project-temp'),
            },
            isInteractive: vi.fn().mockReturnValue(false),
            isInteractiveShellEnabled: vi.fn().mockReturnValue(false),
            isTopicUpdateNarrationEnabled: vi.fn().mockReturnValue(false),
            isAgentsEnabled: vi.fn().mockReturnValue(false),
            getModel: vi.fn().mockReturnValue('auto'),
            getActiveModel: vi.fn().mockReturnValue(PREVIEW_GEMINI_MODEL),
            getPreviewFeatures: vi.fn().mockReturnValue(true),
            getAgentRegistry: vi.fn().mockReturnValue({
                getDirectoryContext: vi.fn().mockReturnValue('Mock Agent Directory'),
                getAllDefinitions: vi.fn().mockReturnValue([]),
            }),
            getSkillManager: vi.fn().mockReturnValue({
                getSkills: vi.fn().mockReturnValue([]),
            }),
            getApprovedPlanPath: vi.fn().mockReturnValue(undefined),
            isTrackerEnabled: vi.fn().mockReturnValue(false),
            get config() {
                return this;
            },
            get toolRegistry() {
                return mockToolRegistry;
            },
        };
        const prompt = getCoreSystemPrompt(testConfig);
        if (expectCodebaseInvestigator) {
            expect(prompt).toContain(`Utilize specialized sub-agents (e.g., \`codebase_investigator\`) as the primary mechanism for initial discovery`);
            expect(prompt).not.toContain('Use `grep_search` and `glob` search tools extensively');
        }
        else {
            expect(prompt).not.toContain(`Utilize specialized sub-agents (e.g., \`codebase_investigator\`) as the primary mechanism for initial discovery`);
            expect(prompt).toContain('Use `grep_search` and `glob` search tools extensively');
        }
        expect(prompt).toMatchSnapshot();
    });
    describe('ApprovalMode in System Prompt', () => {
        // Shared plan mode test fixtures
        const readOnlyMcpTool = new DiscoveredMCPTool({}, 'readonly-server', 'read_data', 'A read-only MCP tool', {}, {}, false, true);
        // Represents the full set of tools allowed by plan.toml policy
        // (including a read-only MCP tool that passes annotation matching).
        // Non-read-only MCP tools are excluded by the policy engine and
        // never appear in getAllTools().
        const planModeTools = [
            { name: 'glob' },
            { name: 'grep_search' },
            { name: 'read_file' },
            { name: 'ask_user' },
            { name: 'exit_plan_mode' },
            { name: 'write_file' },
            { name: 'replace' },
            readOnlyMcpTool,
        ];
        const setupPlanMode = () => {
            vi.mocked(mockConfig.getActiveModel).mockReturnValue(PREVIEW_GEMINI_MODEL);
            vi.mocked(mockConfig.getApprovalMode).mockReturnValue(ApprovalMode.PLAN);
            vi.mocked(mockConfig.toolRegistry.getAllTools).mockReturnValue(planModeTools);
        };
        it('should include PLAN mode instructions', () => {
            setupPlanMode();
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(prompt).toContain('# Active Approval Mode: Plan');
            // Read-only MCP tool should appear with server name
            expect(prompt).toContain('`mcp_readonly-server_read_data` (readonly-server)');
            // Non-read-only MCP tool should not appear (excluded by policy)
            expect(prompt).not.toContain('`mcp_nonreadonly-server_write_data` (nonreadonly-server)');
            expect(prompt).toMatchSnapshot();
        });
        it('should NOT include approval mode instructions for DEFAULT mode', () => {
            vi.mocked(mockConfig.getApprovalMode).mockReturnValue(ApprovalMode.DEFAULT);
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(prompt).not.toContain('# Active Approval Mode: Plan');
            expect(prompt).toMatchSnapshot();
        });
        it('should include read-only MCP tools but not non-read-only MCP tools in PLAN mode', () => {
            setupPlanMode();
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(prompt).toContain('`mcp_readonly-server_read_data` (readonly-server)');
            expect(prompt).not.toContain('`mcp_nonreadonly-server_write_data` (nonreadonly-server)');
        });
        it('should only list available tools in PLAN mode', () => {
            // Use a smaller subset than the full planModeTools to verify
            // that only tools returned by getAllTools() appear in the prompt.
            const subsetTools = [
                { name: 'glob' },
                { name: 'read_file' },
                { name: 'ask_user' },
            ];
            vi.mocked(mockConfig.getActiveModel).mockReturnValue(PREVIEW_GEMINI_MODEL);
            vi.mocked(mockConfig.getApprovalMode).mockReturnValue(ApprovalMode.PLAN);
            vi.mocked(mockConfig.toolRegistry.getAllTools).mockReturnValue(subsetTools);
            const prompt = getCoreSystemPrompt(mockConfig);
            // Should include enabled tools
            expect(prompt).toContain('`glob`');
            expect(prompt).toContain('`read_file`');
            expect(prompt).toContain('`ask_user`');
            // Should NOT include tools not in getAllTools()
            expect(prompt).not.toContain('`google_web_search`');
            expect(prompt).not.toContain('`list_directory`');
            expect(prompt).not.toContain('`grep_search`');
        });
        describe('Approved Plan in Plan Mode', () => {
            beforeEach(() => {
                setupPlanMode();
                vi.mocked(mockConfig.storage.getPlansDir).mockReturnValue('/tmp/plans');
            });
            it('should include approved plan path when set in config', () => {
                const planPath = '/tmp/plans/feature-x.md';
                vi.mocked(mockConfig.getApprovedPlanPath).mockReturnValue(planPath);
                const prompt = getCoreSystemPrompt(mockConfig);
                expect(prompt).toMatchSnapshot();
            });
            it('should NOT include approved plan section if no plan is set in config', () => {
                vi.mocked(mockConfig.getApprovedPlanPath).mockReturnValue(undefined);
                const prompt = getCoreSystemPrompt(mockConfig);
                expect(prompt).toMatchSnapshot();
            });
        });
        it('should include YOLO mode instructions in interactive mode', () => {
            vi.mocked(mockConfig.getApprovalMode).mockReturnValue(ApprovalMode.YOLO);
            vi.mocked(mockConfig.isInteractive).mockReturnValue(true);
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(prompt).toContain('# Autonomous Mode (YOLO)');
            expect(prompt).toContain('Only use the `ask_user` tool if');
        });
        it('should NOT include YOLO mode instructions in non-interactive mode', () => {
            vi.mocked(mockConfig.getApprovalMode).mockReturnValue(ApprovalMode.YOLO);
            vi.mocked(mockConfig.isInteractive).mockReturnValue(false);
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(prompt).not.toContain('# Autonomous Mode (YOLO)');
        });
        it('should NOT include YOLO mode instructions for DEFAULT mode', () => {
            vi.mocked(mockConfig.getApprovalMode).mockReturnValue(ApprovalMode.DEFAULT);
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(prompt).not.toContain('# Autonomous Mode (YOLO)');
        });
    });
    describe('Platform-specific and Background Process instructions', () => {
        it('should include Windows-specific shell efficiency commands on win32', () => {
            mockPlatform('win32');
            vi.mocked(mockConfig.getActiveModel).mockReturnValue(DEFAULT_GEMINI_FLASH_LITE_MODEL);
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(prompt).toContain("using commands like 'type' or 'findstr' (on CMD) and 'Get-Content' or 'Select-String' (on PowerShell)");
            expect(prompt).not.toContain("using commands like 'grep', 'tail', 'head'");
        });
        it('should include generic shell efficiency commands on non-Windows', () => {
            mockPlatform('linux');
            vi.mocked(mockConfig.getActiveModel).mockReturnValue(DEFAULT_GEMINI_FLASH_LITE_MODEL);
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(prompt).toContain("using commands like 'grep', 'tail', 'head'");
            expect(prompt).not.toContain("using commands like 'type' or 'findstr' (on CMD) and 'Get-Content' or 'Select-String' (on PowerShell)");
        });
        it('should use is_background parameter in background process instructions', () => {
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(prompt).toContain('To run a command in the background, set the `is_background` parameter to true.');
            expect(prompt).not.toContain('via `&`');
        });
        it("should include 'tab' instructions when interactive shell is enabled", () => {
            vi.mocked(mockConfig.getActiveModel).mockReturnValue(PREVIEW_GEMINI_MODEL);
            vi.mocked(mockConfig.isInteractive).mockReturnValue(true);
            vi.mocked(mockConfig.isInteractiveShellEnabled).mockReturnValue(true);
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(prompt).toContain('tab');
        });
        it("should NOT include 'tab' instructions when interactive shell is disabled", () => {
            vi.mocked(mockConfig.getActiveModel).mockReturnValue(PREVIEW_GEMINI_MODEL);
            vi.mocked(mockConfig.isInteractive).mockReturnValue(true);
            vi.mocked(mockConfig.isInteractiveShellEnabled).mockReturnValue(false);
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(prompt).not.toContain('`tab`');
        });
    });
    it('should include approved plan instructions when approvedPlanPath is set', () => {
        const planPath = '/path/to/approved/plan.md';
        vi.mocked(mockConfig.getApprovedPlanPath).mockReturnValue(planPath);
        const prompt = getCoreSystemPrompt(mockConfig);
        expect(prompt).toMatchSnapshot();
    });
    it('should include modern approved plan instructions with completion in DEFAULT mode when approvedPlanPath is set', () => {
        const planPath = '/tmp/plans/feature-x.md';
        vi.mocked(mockConfig.getApprovedPlanPath).mockReturnValue(planPath);
        vi.mocked(mockConfig.getActiveModel).mockReturnValue(PREVIEW_GEMINI_MODEL);
        vi.mocked(mockConfig.getApprovalMode).mockReturnValue(ApprovalMode.DEFAULT);
        const prompt = getCoreSystemPrompt(mockConfig);
        expect(prompt).toContain('2. **Strategy:** An approved plan is available for this task');
        expect(prompt).toContain('provide a **final summary** of the work completed against the plan');
        expect(prompt).toMatchSnapshot();
    });
    it('should include planning phase suggestion when enter_plan_mode tool is enabled', () => {
        vi.mocked(mockConfig.getActiveModel).mockReturnValue(PREVIEW_GEMINI_MODEL);
        vi.mocked(mockConfig.toolRegistry.getAllToolNames).mockReturnValue([
            'enter_plan_mode',
        ]);
        const prompt = getCoreSystemPrompt(mockConfig);
        expect(prompt).toContain('If the request is ambiguous, broad in scope, or involves architectural decisions or cross-cutting changes, use the `enter_plan_mode` tool to safely research and design your strategy. Do NOT use Plan Mode for straightforward bug fixes, answering questions, or simple inquiries.');
        expect(prompt).toMatchSnapshot();
    });
    describe('GEMINI_SYSTEM_MD environment variable', () => {
        it.each(['false', '0'])('should use default prompt when GEMINI_SYSTEM_MD is "%s"', (value) => {
            vi.stubEnv('GEMINI_SYSTEM_MD', value);
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(fs.readFileSync).not.toHaveBeenCalled();
            expect(prompt).not.toContain('custom system prompt');
        });
        it('should throw error if GEMINI_SYSTEM_MD points to a non-existent file', () => {
            const customPath = '/non/existent/path/system.md';
            vi.stubEnv('GEMINI_SYSTEM_MD', customPath);
            vi.mocked(fs.existsSync).mockReturnValue(false);
            expect(() => getCoreSystemPrompt(mockConfig)).toThrow(`missing system prompt file '${path.resolve(customPath)}'`);
        });
        it.each(['true', '1'])('should read from default path when GEMINI_SYSTEM_MD is "%s"', (value) => {
            const defaultPath = path.resolve(path.join(GEMINI_DIR, 'system.md'));
            vi.stubEnv('GEMINI_SYSTEM_MD', value);
            vi.mocked(fs.existsSync).mockReturnValue(true);
            vi.mocked(fs.readFileSync).mockReturnValue('custom system prompt');
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(fs.readFileSync).toHaveBeenCalledWith(defaultPath, 'utf8');
            expect(prompt).toBe('custom system prompt');
        });
        it('should read from custom path when GEMINI_SYSTEM_MD provides one, preserving case', () => {
            const customPath = path.resolve('/custom/path/SyStEm.Md');
            vi.stubEnv('GEMINI_SYSTEM_MD', customPath);
            vi.mocked(fs.existsSync).mockReturnValue(true);
            vi.mocked(fs.readFileSync).mockReturnValue('custom system prompt');
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(fs.readFileSync).toHaveBeenCalledWith(customPath, 'utf8');
            expect(prompt).toBe('custom system prompt');
        });
        it('should expand tilde in custom path when GEMINI_SYSTEM_MD is set', () => {
            const homeDir = '/Users/test';
            vi.spyOn(os, 'homedir').mockReturnValue(homeDir);
            const customPath = '~/custom/system.md';
            const expectedPath = path.join(homeDir, 'custom/system.md');
            vi.stubEnv('GEMINI_SYSTEM_MD', customPath);
            vi.mocked(fs.existsSync).mockReturnValue(true);
            vi.mocked(fs.readFileSync).mockReturnValue('custom system prompt');
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(fs.readFileSync).toHaveBeenCalledWith(path.resolve(expectedPath), 'utf8');
            expect(prompt).toBe('custom system prompt');
        });
    });
    describe('GEMINI_WRITE_SYSTEM_MD environment variable', () => {
        it.each(['false', '0'])('should not write to file when GEMINI_WRITE_SYSTEM_MD is "%s"', (value) => {
            vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', value);
            getCoreSystemPrompt(mockConfig);
            expect(fs.writeFileSync).not.toHaveBeenCalled();
        });
        it.each(['true', '1'])('should write to default path when GEMINI_WRITE_SYSTEM_MD is "%s"', (value) => {
            const defaultPath = path.resolve(path.join(GEMINI_DIR, 'system.md'));
            vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', value);
            getCoreSystemPrompt(mockConfig);
            expect(fs.writeFileSync).toHaveBeenCalledWith(defaultPath, expect.any(String));
        });
        it('should write to custom path when GEMINI_WRITE_SYSTEM_MD provides one', () => {
            const customPath = path.resolve('/custom/path/system.md');
            vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', customPath);
            getCoreSystemPrompt(mockConfig);
            expect(fs.writeFileSync).toHaveBeenCalledWith(customPath, expect.any(String));
        });
        it.each([
            ['~/custom/system.md', 'custom/system.md'],
            ['~', ''],
        ])('should expand tilde in custom path when GEMINI_WRITE_SYSTEM_MD is "%s"', (customPath, relativePath) => {
            const homeDir = '/Users/test';
            vi.spyOn(os, 'homedir').mockReturnValue(homeDir);
            const expectedPath = relativePath
                ? path.join(homeDir, relativePath)
                : homeDir;
            vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', customPath);
            getCoreSystemPrompt(mockConfig);
            expect(fs.writeFileSync).toHaveBeenCalledWith(path.resolve(expectedPath), expect.any(String));
        });
    });
});
describe('resolvePathFromEnv helper function', () => {
    beforeEach(() => {
        vi.resetAllMocks();
    });
    describe('when envVar is undefined, empty, or whitespace', () => {
        it.each([
            ['undefined', undefined],
            ['empty string', ''],
            ['whitespace only', '   \n\t  '],
        ])('should return null for %s', (_, input) => {
            const result = resolvePathFromEnv(input);
            expect(result).toEqual({
                isSwitch: false,
                value: null,
                isDisabled: false,
            });
        });
    });
    describe('when envVar is a boolean-like string', () => {
        it.each([
            ['"0" as disabled switch', '0', '0', true],
            ['"false" as disabled switch', 'false', 'false', true],
            ['"1" as enabled switch', '1', '1', false],
            ['"true" as enabled switch', 'true', 'true', false],
            ['"FALSE" (case-insensitive)', 'FALSE', 'false', true],
            ['"TRUE" (case-insensitive)', 'TRUE', 'true', false],
        ])('should handle %s', (_, input, expectedValue, isDisabled) => {
            const result = resolvePathFromEnv(input);
            expect(result).toEqual({
                isSwitch: true,
                value: expectedValue,
                isDisabled,
            });
        });
    });
    describe('when envVar is a file path', () => {
        it.each([['/absolute/path/file.txt'], ['relative/path/file.txt']])('should resolve path: %s', (input) => {
            const result = resolvePathFromEnv(input);
            expect(result).toEqual({
                isSwitch: false,
                value: path.resolve(input),
                isDisabled: false,
            });
        });
        it.each([
            ['~/documents/file.txt', 'documents/file.txt'],
            ['~', ''],
        ])('should expand tilde path: %s', (input, homeRelativePath) => {
            const homeDir = '/Users/test';
            vi.spyOn(os, 'homedir').mockReturnValue(homeDir);
            const result = resolvePathFromEnv(input);
            expect(result).toEqual({
                isSwitch: false,
                value: path.resolve(homeRelativePath ? path.join(homeDir, homeRelativePath) : homeDir),
                isDisabled: false,
            });
        });
        it('should handle os.homedir() errors gracefully', () => {
            vi.spyOn(os, 'homedir').mockImplementation(() => {
                throw new Error('Cannot resolve home directory');
            });
            const consoleSpy = vi
                .spyOn(debugLogger, 'warn')
                .mockImplementation(() => { });
            const result = resolvePathFromEnv('~/documents/file.txt');
            expect(result).toEqual({
                isSwitch: false,
                value: null,
                isDisabled: false,
            });
            expect(consoleSpy).toHaveBeenCalledWith('Could not resolve home directory for path: ~/documents/file.txt', expect.any(Error));
            consoleSpy.mockRestore();
        });
    });
});
//# sourceMappingURL=prompts.test.js.map