"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadNestedConfigurations = loadNestedConfigurations;
exports.loadSingleConfiguration = loadSingleConfiguration;
exports.processHierarchicalConfigurations = processHierarchicalConfigurations;
exports.processSingleConfiguration = processSingleConfiguration;
exports.applyConfigurationsToAgents = applyConfigurationsToAgents;
exports.updateGitignore = updateGitignore;
const path = __importStar(require("path"));
const fs_1 = require("fs");
const toml_1 = require("@iarna/toml");
const FileSystemUtils = __importStar(require("./FileSystemUtils"));
const RuleProcessor_1 = require("./RuleProcessor");
const ConfigLoader_1 = require("./ConfigLoader");
const GitignoreUtils_1 = require("./GitignoreUtils");
const merge_1 = require("../mcp/merge");
const mcp_1 = require("../paths/mcp");
const propagateOpenHandsMcp_1 = require("../mcp/propagateOpenHandsMcp");
const propagateOpenCodeMcp_1 = require("../mcp/propagateOpenCodeMcp");
const agent_utils_1 = require("../agents/agent-utils");
const capabilities_1 = require("../mcp/capabilities");
const constants_1 = require("../constants");
async function loadNestedConfigurations(projectRoot, configPath, localOnly, resolvedNested) {
    const { dirs: rulerDirs } = await findRulerDirectories(projectRoot, localOnly, true);
    const results = [];
    const rulerDirConfigs = await processIndependentRulerDirs(rulerDirs);
    for (const { rulerDir, files } of rulerDirConfigs) {
        const config = await loadConfigForRulerDir(rulerDir, configPath, resolvedNested);
        results.push(await createHierarchicalConfiguration(rulerDir, files, config, configPath));
    }
    return results;
}
/**
 * Processes each .ruler directory independently, returning configuration for each.
 * Each .ruler directory gets its own rules (not merged with others).
 */
async function processIndependentRulerDirs(rulerDirs) {
    const results = [];
    // Process each .ruler directory independently
    for (const rulerDir of rulerDirs) {
        const files = await FileSystemUtils.readMarkdownFiles(rulerDir);
        results.push({ rulerDir, files });
    }
    return results;
}
async function createHierarchicalConfiguration(rulerDir, files, config, cliConfigPath) {
    await warnAboutLegacyMcpJson(rulerDir);
    const concatenatedRules = (0, RuleProcessor_1.concatenateRules)(files, path.dirname(rulerDir));
    const directoryRoot = path.dirname(rulerDir);
    const localConfigPath = path.join(rulerDir, 'ruler.toml');
    let configPathToUse = cliConfigPath;
    try {
        await fs_1.promises.access(localConfigPath);
        configPathToUse = localConfigPath;
    }
    catch {
        // fall back to CLI config or default resolution
    }
    const { loadUnifiedConfig } = await Promise.resolve().then(() => __importStar(require('./UnifiedConfigLoader')));
    const unifiedConfig = await loadUnifiedConfig({
        projectRoot: directoryRoot,
        configPath: configPathToUse,
    });
    let rulerMcpJson = null;
    if (unifiedConfig.mcp && Object.keys(unifiedConfig.mcp.servers).length > 0) {
        rulerMcpJson = {
            mcpServers: unifiedConfig.mcp.servers,
        };
    }
    return {
        rulerDir,
        config,
        concatenatedRules,
        rulerMcpJson,
    };
}
async function loadConfigForRulerDir(rulerDir, cliConfigPath, resolvedNested) {
    const directoryRoot = path.dirname(rulerDir);
    const localConfigPath = path.join(rulerDir, 'ruler.toml');
    let hasLocalConfig = false;
    try {
        await fs_1.promises.access(localConfigPath);
        hasLocalConfig = true;
    }
    catch {
        hasLocalConfig = false;
    }
    const loaded = await (0, ConfigLoader_1.loadConfig)({
        projectRoot: directoryRoot,
        configPath: hasLocalConfig ? localConfigPath : cliConfigPath,
    });
    const cloned = cloneLoadedConfig(loaded);
    if (resolvedNested) {
        if (hasLocalConfig && loaded.nestedDefined && loaded.nested === false) {
            (0, constants_1.logWarn)(`Nested mode is enabled but ${localConfigPath} sets nested = false. Continuing with nested processing.`);
        }
        cloned.nested = true;
        cloned.nestedDefined = true;
    }
    return cloned;
}
function cloneLoadedConfig(config) {
    const clonedAgentConfigs = {};
    for (const [agent, agentConfig] of Object.entries(config.agentConfigs)) {
        clonedAgentConfigs[agent] = {
            ...agentConfig,
            mcp: agentConfig.mcp ? { ...agentConfig.mcp } : undefined,
        };
    }
    return {
        defaultAgents: config.defaultAgents ? [...config.defaultAgents] : undefined,
        agentConfigs: clonedAgentConfigs,
        cliAgents: config.cliAgents ? [...config.cliAgents] : undefined,
        mcp: config.mcp ? { ...config.mcp } : undefined,
        gitignore: config.gitignore ? { ...config.gitignore } : undefined,
        nested: config.nested,
        nestedDefined: config.nestedDefined,
    };
}
/**
 * Finds ruler directories based on the specified mode.
 */
async function findRulerDirectories(projectRoot, localOnly, hierarchical) {
    if (hierarchical) {
        const dirs = await FileSystemUtils.findAllRulerDirs(projectRoot);
        const allDirs = [...dirs];
        // Add global config if not local-only
        if (!localOnly) {
            const globalDir = await FileSystemUtils.findGlobalRulerDir();
            if (globalDir) {
                allDirs.push(globalDir);
            }
        }
        if (allDirs.length === 0) {
            throw (0, constants_1.createRulerError)(`.ruler directory not found`, `Searched from: ${projectRoot}`);
        }
        return { dirs: allDirs, primaryDir: allDirs[0] };
    }
    else {
        const dir = await FileSystemUtils.findRulerDir(projectRoot, !localOnly);
        if (!dir) {
            throw (0, constants_1.createRulerError)(`.ruler directory not found`, `Searched from: ${projectRoot}`);
        }
        return { dirs: [dir], primaryDir: dir };
    }
}
/**
 * Warns about legacy mcp.json files if they exist.
 */
async function warnAboutLegacyMcpJson(rulerDir) {
    try {
        const legacyMcpPath = path.join(rulerDir, 'mcp.json');
        await fs_1.promises.access(legacyMcpPath);
        (0, constants_1.logWarn)('Warning: Using legacy .ruler/mcp.json. Please migrate to ruler.toml. This fallback will be removed in a future release.');
    }
    catch {
        // ignore
    }
}
/**
 * Loads configuration for single-directory mode (existing behavior).
 */
async function loadSingleConfiguration(projectRoot, configPath, localOnly) {
    // Find the single ruler directory
    const { dirs: rulerDirs, primaryDir } = await findRulerDirectories(projectRoot, localOnly, false);
    // Warn about legacy mcp.json
    await warnAboutLegacyMcpJson(primaryDir);
    // Load the ruler.toml configuration
    const config = await (0, ConfigLoader_1.loadConfig)({
        projectRoot,
        configPath,
    });
    // Read rule files
    const files = await FileSystemUtils.readMarkdownFiles(rulerDirs[0]);
    // Concatenate rules
    const concatenatedRules = (0, RuleProcessor_1.concatenateRules)(files, path.dirname(primaryDir));
    // Load unified config to get merged MCP configuration
    const { loadUnifiedConfig } = await Promise.resolve().then(() => __importStar(require('./UnifiedConfigLoader')));
    const unifiedConfig = await loadUnifiedConfig({ projectRoot, configPath });
    // Synthesize rulerMcpJson from unified MCP bundle for backward compatibility
    let rulerMcpJson = null;
    if (unifiedConfig.mcp && Object.keys(unifiedConfig.mcp.servers).length > 0) {
        rulerMcpJson = {
            mcpServers: unifiedConfig.mcp.servers,
        };
    }
    return {
        config,
        concatenatedRules,
        rulerMcpJson,
    };
}
/**
 * Processes hierarchical configurations by applying rules to each .ruler directory independently.
 * Each directory gets its own set of rules and generates its own agent files.
 * @param agents Array of agents to process
 * @param configurations Array of hierarchical configurations for each .ruler directory
 * @param verbose Whether to enable verbose logging
 * @param dryRun Whether to perform a dry run
 * @param cliMcpEnabled Whether MCP is enabled via CLI
 * @param cliMcpStrategy MCP strategy from CLI
 * @returns Promise resolving to array of generated file paths
 */
async function processHierarchicalConfigurations(agents, configurations, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup = true) {
    const allGeneratedPaths = [];
    for (const config of configurations) {
        (0, constants_1.logVerboseInfo)(`Processing .ruler directory: ${config.rulerDir}`, verbose, dryRun);
        const rulerRoot = path.dirname(config.rulerDir);
        const paths = await applyConfigurationsToAgents(agents, config.concatenatedRules, config.rulerMcpJson, config.config, rulerRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
        const normalizedPaths = paths.map((p) => path.isAbsolute(p) ? p : path.join(rulerRoot, p));
        allGeneratedPaths.push(...normalizedPaths);
    }
    return allGeneratedPaths;
}
/**
 * Processes a single configuration by applying rules to all selected agents.
 * All rules are concatenated and applied to generate agent files in the project root.
 * @param agents Array of agents to process
 * @param configuration Single ruler configuration with concatenated rules
 * @param projectRoot Root directory of the project
 * @param verbose Whether to enable verbose logging
 * @param dryRun Whether to perform a dry run
 * @param cliMcpEnabled Whether MCP is enabled via CLI
 * @param cliMcpStrategy MCP strategy from CLI
 * @returns Promise resolving to array of generated file paths
 */
async function processSingleConfiguration(agents, configuration, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup = true) {
    return await applyConfigurationsToAgents(agents, configuration.concatenatedRules, configuration.rulerMcpJson, configuration.config, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
}
/**
 * Applies configurations to the selected agents (internal function).
 * @param agents Array of agents to process
 * @param concatenatedRules Concatenated rule content
 * @param rulerMcpJson MCP configuration JSON
 * @param config Loaded configuration
 * @param projectRoot Root directory of the project
 * @param verbose Whether to enable verbose logging
 * @param dryRun Whether to perform a dry run
 * @returns Promise resolving to array of generated file paths
 */
async function applyConfigurationsToAgents(agents, concatenatedRules, rulerMcpJson, config, projectRoot, verbose, dryRun, cliMcpEnabled = true, cliMcpStrategy, backup = true) {
    const generatedPaths = [];
    let agentsMdWritten = false;
    for (const agent of agents) {
        (0, constants_1.logInfo)(`Applying rules for ${agent.getName()}...`, dryRun);
        (0, constants_1.logVerbose)(`Processing agent: ${agent.getName()}`, verbose);
        const agentConfig = config.agentConfigs[agent.getIdentifier()];
        const agentRulerMcpJson = rulerMcpJson;
        // Collect output paths for .gitignore
        const outputPaths = (0, agent_utils_1.getAgentOutputPaths)(agent, projectRoot, agentConfig);
        (0, constants_1.logVerbose)(`Agent ${agent.getName()} output paths: ${outputPaths.join(', ')}`, verbose);
        generatedPaths.push(...outputPaths);
        // Only add the backup file paths to the gitignore list if backups are enabled
        if (backup) {
            const backupPaths = outputPaths.map((p) => `${p}.bak`);
            generatedPaths.push(...backupPaths);
        }
        if (dryRun) {
            (0, constants_1.logVerbose)(`DRY RUN: Would write rules to: ${outputPaths.join(', ')}`, verbose);
        }
        else {
            let skipApplyForThisAgent = false;
            if (agent.getIdentifier() === 'jules' ||
                agent.getIdentifier() === 'agentsmd') {
                if (agentsMdWritten) {
                    // Skip rewriting AGENTS.md, but still allow MCP handling below
                    skipApplyForThisAgent = true;
                }
                else {
                    agentsMdWritten = true;
                }
            }
            let finalAgentConfig = agentConfig;
            if (agent.getIdentifier() === 'augmentcode' && agentRulerMcpJson) {
                const resolvedStrategy = cliMcpStrategy ??
                    agentConfig?.mcp?.strategy ??
                    config.mcp?.strategy ??
                    'merge';
                finalAgentConfig = {
                    ...agentConfig,
                    mcp: {
                        ...agentConfig?.mcp,
                        strategy: resolvedStrategy,
                    },
                };
            }
            if (!skipApplyForThisAgent) {
                await agent.applyRulerConfig(concatenatedRules, projectRoot, agentRulerMcpJson, finalAgentConfig, backup);
            }
        }
        // Handle MCP configuration
        await handleMcpConfiguration(agent, agentConfig, config, agentRulerMcpJson, projectRoot, generatedPaths, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
    }
    return generatedPaths;
}
async function handleMcpConfiguration(agent, agentConfig, config, rulerMcpJson, projectRoot, generatedPaths, verbose, dryRun, cliMcpEnabled = true, cliMcpStrategy, backup = true) {
    if (!(0, capabilities_1.agentSupportsMcp)(agent)) {
        (0, constants_1.logVerbose)(`Agent ${agent.getName()} does not support MCP - skipping MCP configuration`, verbose);
        return;
    }
    const dest = await (0, mcp_1.getNativeMcpPath)(agent.getName(), projectRoot);
    const mcpEnabledForAgent = cliMcpEnabled && (agentConfig?.mcp?.enabled ?? config.mcp?.enabled ?? true);
    if (!dest || !mcpEnabledForAgent) {
        return;
    }
    const filteredMcpJson = rulerMcpJson
        ? (0, capabilities_1.filterMcpConfigForAgent)(rulerMcpJson, agent)
        : null;
    if (!filteredMcpJson) {
        (0, constants_1.logVerbose)(`No compatible MCP servers found for ${agent.getName()} - skipping MCP configuration`, verbose);
        return;
    }
    await updateGitignoreForMcpFile(dest, projectRoot, generatedPaths, backup);
    await applyMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, projectRoot, cliMcpStrategy, dryRun, verbose, backup);
}
async function updateGitignoreForMcpFile(dest, projectRoot, generatedPaths, backup = true) {
    if (dest.startsWith(projectRoot)) {
        const relativeDest = path.relative(projectRoot, dest);
        generatedPaths.push(relativeDest);
        if (backup) {
            generatedPaths.push(`${relativeDest}.bak`);
        }
    }
}
function sanitizeMcpTimeoutsForAgent(agent, mcpJson, dryRun) {
    if (agent.supportsMcpTimeout?.()) {
        return mcpJson;
    }
    if (!mcpJson.mcpServers || typeof mcpJson.mcpServers !== 'object') {
        return mcpJson;
    }
    const servers = mcpJson.mcpServers;
    const sanitizedServers = {};
    const strippedTimeouts = [];
    for (const [name, serverDef] of Object.entries(servers)) {
        if (serverDef && typeof serverDef === 'object') {
            const copy = { ...serverDef };
            if ('timeout' in copy) {
                delete copy.timeout;
                strippedTimeouts.push(name);
            }
            sanitizedServers[name] = copy;
        }
        else {
            sanitizedServers[name] = serverDef;
        }
    }
    if (strippedTimeouts.length > 0) {
        (0, constants_1.logWarn)(`${agent.getName()} does not support MCP server timeout configuration; ignoring timeout for: ${strippedTimeouts.join(', ')}`, dryRun);
    }
    return {
        ...mcpJson,
        mcpServers: sanitizedServers,
    };
}
async function applyMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, projectRoot, cliMcpStrategy, dryRun, verbose, backup = true) {
    // Prevent writing MCP configs outside the project root (e.g., legacy home-directory targets)
    if (!dest.startsWith(projectRoot)) {
        (0, constants_1.logVerbose)(`Skipping MCP config for ${agent.getName()} because target path is outside project: ${dest}`, verbose);
        return;
    }
    const agentMcpJson = sanitizeMcpTimeoutsForAgent(agent, filteredMcpJson, dryRun);
    if (agent.getIdentifier() === 'openhands') {
        return await applyOpenHandsMcpConfiguration(agentMcpJson, dest, dryRun, verbose, backup);
    }
    if (agent.getIdentifier() === 'opencode') {
        return await applyOpenCodeMcpConfiguration(agentMcpJson, dest, dryRun, verbose, backup);
    }
    // Agents that handle MCP configuration internally should not have external MCP handling
    if (agent.getIdentifier() === 'zed' ||
        agent.getIdentifier() === 'gemini-cli' ||
        agent.getIdentifier() === 'amazon-q-cli' ||
        agent.getIdentifier() === 'crush') {
        (0, constants_1.logVerbose)(`Skipping external MCP config for ${agent.getName()} - handled internally by agent`, verbose);
        return;
    }
    return await applyStandardMcpConfiguration(agent, agentMcpJson, dest, agentConfig, config, cliMcpStrategy, dryRun, verbose, backup);
}
async function applyOpenHandsMcpConfiguration(filteredMcpJson, dest, dryRun, verbose, backup = true) {
    if (dryRun) {
        (0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config by updating TOML file: ${dest}`, verbose);
    }
    else {
        await (0, propagateOpenHandsMcp_1.propagateMcpToOpenHands)(filteredMcpJson, dest, backup);
    }
}
async function applyOpenCodeMcpConfiguration(filteredMcpJson, dest, dryRun, verbose, backup = true) {
    if (dryRun) {
        (0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config by updating OpenCode config file: ${dest}`, verbose);
    }
    else {
        await (0, propagateOpenCodeMcp_1.propagateMcpToOpenCode)(filteredMcpJson, dest, backup);
    }
}
/**
 * Transform MCP server types for Claude Code compatibility.
 * Claude expects "http" for HTTP servers and "sse" for SSE servers, not "remote".
 */
function transformMcpForClaude(mcpJson) {
    if (!mcpJson.mcpServers || typeof mcpJson.mcpServers !== 'object') {
        return mcpJson;
    }
    const transformedMcp = { ...mcpJson };
    const transformedServers = {};
    for (const [name, serverDef] of Object.entries(mcpJson.mcpServers)) {
        if (serverDef && typeof serverDef === 'object') {
            const server = serverDef;
            const transformedServer = { ...server };
            // Transform type: "remote" to appropriate Claude types
            if (server.type === 'remote' &&
                server.url &&
                typeof server.url === 'string') {
                const url = server.url;
                // Check if URL suggests SSE (contains /sse path segment)
                if (/\/sse(\/|$)/i.test(url)) {
                    transformedServer.type = 'sse';
                }
                else {
                    transformedServer.type = 'http';
                }
            }
            transformedServers[name] = transformedServer;
        }
        else {
            transformedServers[name] = serverDef;
        }
    }
    transformedMcp.mcpServers = transformedServers;
    return transformedMcp;
}
/**
 * Transform MCP server types for Kilo Code compatibility.
 * Kilo Code expects "streamable-http" for remote HTTP servers, not "remote".
 */
function transformMcpForKiloCode(mcpJson) {
    if (!mcpJson.mcpServers || typeof mcpJson.mcpServers !== 'object') {
        return mcpJson;
    }
    const transformedMcp = { ...mcpJson };
    const transformedServers = {};
    for (const [name, serverDef] of Object.entries(mcpJson.mcpServers)) {
        if (serverDef && typeof serverDef === 'object') {
            const server = serverDef;
            const transformedServer = { ...server };
            // Transform type: "remote" to "streamable-http" for HTTP-based servers
            if (server.type === 'remote' &&
                server.url &&
                typeof server.url === 'string') {
                transformedServer.type = 'streamable-http';
            }
            transformedServers[name] = transformedServer;
        }
        else {
            transformedServers[name] = serverDef;
        }
    }
    transformedMcp.mcpServers = transformedServers;
    return transformedMcp;
}
/**
 * Transform MCP server types for Factory Droid compatibility.
 * Factory Droid expects "http" for remote HTTP servers, not "remote".
 */
function transformMcpForFactoryDroid(mcpJson) {
    if (!mcpJson.mcpServers || typeof mcpJson.mcpServers !== 'object') {
        return mcpJson;
    }
    const transformedMcp = { ...mcpJson };
    const transformedServers = {};
    for (const [name, serverDef] of Object.entries(mcpJson.mcpServers)) {
        if (serverDef && typeof serverDef === 'object') {
            const server = serverDef;
            const transformedServer = { ...server };
            if (server.type === 'remote' &&
                server.url &&
                typeof server.url === 'string') {
                transformedServer.type = 'http';
            }
            transformedServers[name] = transformedServer;
        }
        else {
            transformedServers[name] = serverDef;
        }
    }
    transformedMcp.mcpServers = transformedServers;
    return transformedMcp;
}
async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, cliMcpStrategy, dryRun, verbose, backup = true) {
    const strategy = cliMcpStrategy ??
        agentConfig?.mcp?.strategy ??
        config.mcp?.strategy ??
        'merge';
    const serverKey = agent.getMcpServerKey?.() ?? 'mcpServers';
    // Skip agents with empty server keys (e.g., AgentsMdAgent, GooseAgent)
    if (serverKey === '') {
        (0, constants_1.logVerbose)(`Skipping MCP config for ${agent.getName()} - agent has empty server key`, verbose);
        return;
    }
    (0, constants_1.logVerbose)(`Applying filtered MCP config for ${agent.getName()} with strategy: ${strategy} and key: ${serverKey}`, verbose);
    if (dryRun) {
        (0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config to: ${dest}`, verbose);
    }
    else {
        // Transform MCP config for agent-specific compatibility
        let mcpToMerge = filteredMcpJson;
        if (agent.getIdentifier() === 'claude') {
            mcpToMerge = transformMcpForClaude(filteredMcpJson);
        }
        else if (agent.getIdentifier() === 'kilocode') {
            mcpToMerge = transformMcpForKiloCode(filteredMcpJson);
        }
        else if (agent.getIdentifier() === 'factory') {
            mcpToMerge = transformMcpForFactoryDroid(filteredMcpJson);
        }
        const CODEX_AGENT_ID = 'codex';
        const isCodexToml = agent.getIdentifier() === CODEX_AGENT_ID && dest.endsWith('.toml');
        let existing = await (0, mcp_1.readNativeMcp)(dest);
        if (isCodexToml) {
            try {
                const tomlContent = await fs_1.promises.readFile(dest, 'utf8');
                existing = (0, toml_1.parse)(tomlContent);
            }
            catch (error) {
                (0, constants_1.logVerbose)(`Failed to read Codex MCP TOML at ${dest}: ${error.message}`, verbose);
                // ignore missing or invalid TOML, fall back to previously read value
            }
        }
        let merged = (0, merge_1.mergeMcp)(existing, mcpToMerge, strategy, serverKey);
        if (isCodexToml) {
            const { [serverKey]: servers, ...rest } = merged;
            merged = {
                ...rest,
                // Codex CLI expects MCP servers under mcp_servers in config.toml.
                mcp_servers: servers ?? {},
            };
        }
        // Firebase Studio (IDX) expects no "type" fields in .idx/mcp.json server entries.
        // Sanitize merged config by stripping 'type' from each server when targeting Firebase.
        const sanitizeForFirebase = (obj) => {
            if (agent.getIdentifier() !== 'firebase')
                return obj;
            const out = { ...obj };
            const servers = out[serverKey] || {};
            const cleanedServers = {};
            for (const [name, def] of Object.entries(servers)) {
                if (def && typeof def === 'object') {
                    const copy = { ...def };
                    delete copy.type;
                    cleanedServers[name] = copy;
                }
                else {
                    cleanedServers[name] = def;
                }
            }
            out[serverKey] = cleanedServers;
            return out;
        };
        // Gemini CLI (since v0.21.0) no longer accepts the "type" field in MCP server entries.
        // Following the MCP spec update from Nov 25, 2025, the transport type is now inferred
        // from the presence of specific keys (command/args -> stdio, url -> sse/http).
        // Sanitize merged config by stripping 'type' from each server when targeting Gemini.
        const sanitizeForGemini = (obj) => {
            if (agent.getIdentifier() !== 'gemini-cli')
                return obj;
            const out = { ...obj };
            const servers = out[serverKey] || {};
            const cleanedServers = {};
            for (const [name, def] of Object.entries(servers)) {
                if (def && typeof def === 'object') {
                    const copy = { ...def };
                    delete copy.type;
                    cleanedServers[name] = copy;
                }
                else {
                    cleanedServers[name] = def;
                }
            }
            out[serverKey] = cleanedServers;
            return out;
        };
        let toWrite = sanitizeForFirebase(merged);
        toWrite = sanitizeForGemini(toWrite);
        // Only backup and write if content would actually change (idempotent)
        const currentContent = isCodexToml
            ? (0, toml_1.stringify)(existing)
            : JSON.stringify(existing, null, 2);
        const newContent = isCodexToml
            ? (0, toml_1.stringify)(toWrite)
            : JSON.stringify(toWrite, null, 2);
        if (currentContent !== newContent) {
            if (backup) {
                const { backupFile } = await Promise.resolve().then(() => __importStar(require('../core/FileSystemUtils')));
                await backupFile(dest);
            }
            if (isCodexToml) {
                await FileSystemUtils.writeGeneratedFile(dest, (0, toml_1.stringify)(toWrite));
            }
            else {
                await (0, mcp_1.writeNativeMcp)(dest, toWrite);
            }
        }
        else {
            (0, constants_1.logVerbose)(`MCP config for ${agent.getName()} is already up to date - skipping backup and write`, verbose);
        }
    }
}
/**
 * Updates the .gitignore file with generated paths.
 * @param projectRoot Root directory of the project
 * @param generatedPaths Array of generated file paths
 * @param config Loaded configuration
 * @param cliGitignoreEnabled CLI gitignore setting
 * @param dryRun Whether to perform a dry run
 * @param cliGitignoreLocal CLI toggle for .git/info/exclude usage
 */
async function updateGitignore(projectRoot, generatedPaths, config, cliGitignoreEnabled, dryRun, cliGitignoreLocal) {
    // Configuration precedence: CLI > TOML > Default (enabled)
    let gitignoreEnabled;
    if (cliGitignoreEnabled !== undefined) {
        gitignoreEnabled = cliGitignoreEnabled;
    }
    else if (config.gitignore?.enabled !== undefined) {
        gitignoreEnabled = config.gitignore.enabled;
    }
    else {
        gitignoreEnabled = true; // Default enabled
    }
    const gitignoreTarget = cliGitignoreLocal !== undefined
        ? cliGitignoreLocal
            ? '.git/info/exclude'
            : '.gitignore'
        : config.gitignore?.local
            ? '.git/info/exclude'
            : '.gitignore';
    if (gitignoreEnabled && generatedPaths.length > 0) {
        const uniquePaths = [...new Set(generatedPaths)];
        // Note: Individual backup patterns are added per-file in the collection phase
        // No need to add a broad *.bak pattern here
        if (uniquePaths.length > 0) {
            if (dryRun) {
                (0, constants_1.logInfo)(`Would update ${gitignoreTarget} with ${uniquePaths.length} unique path(s): ${uniquePaths.join(', ')}`, dryRun);
            }
            else {
                await (0, GitignoreUtils_1.updateGitignore)(projectRoot, uniquePaths, gitignoreTarget);
                (0, constants_1.logInfo)(`Updated ${gitignoreTarget} with ${uniquePaths.length} unique path(s) in the Ruler block.`, dryRun);
            }
        }
    }
}
