"use strict";
/**
 * CodyIdeAdapter -- Sourcegraph Cody adapter.
 *
 * Sourcegraph Cody is an enterprise code intelligence + AI assistant that
 * wraps the Sourcegraph platform's code-search and context-fetch engine.
 * Customer base: enterprise dev orgs, regulated industries (banks, healthcare,
 * large government agencies).
 *
 * Deployment modes:
 *   sourcegraph-cloud -- Sourcegraph-managed SaaS
 *   self-hosted       -- Customer-operated Sourcegraph instance (air-gapped,
 *                        private cloud, on-premise)
 *   free-pro          -- Individual Cody Free / Cody Pro; no enterprise features
 *                        (no embeddings index, limited context window)
 *
 * Hook surfaces (all seven are separate governable surfaces):
 *   codeSearchRequested(query, repos)  -- code-search query gate; context
 *     returned from code search can contain secrets; pre-result gate.
 *   contextFetchRequested(filePaths)   -- fetch of repository context
 *     (files, symbols, docs) before injection into the AI prompt.
 *   autocompleteRequested(file, cursor, context) -- inline autocomplete gate.
 *   chatRequested(prompt, repoContext) -- chat request gate; Cody chat can
 *     have attached repo context injected into the prompt window.
 *   commitMessageRequested(diff)       -- Cody-generated commit message gate;
 *     classify diff for secrets before generation. (PT-ADAPT-CODY-1 indirectly)
 *   recipeExecutionRequested(recipe, args) -- Cody "recipes" (explain code,
 *     generate tests, etc.) are structured AI workflows. Recipe execution is
 *     treated as a bounded autonomous operation requiring pack compliance.
 *   embeddingsIndexRequested(repos)    -- Embeddings index build for a repo;
 *     only available in self-hosted and sourcegraph-cloud modes. free-pro mode
 *     does NOT support embeddings; the adapter explicitly blocks this surface
 *     in free-pro mode regardless of pack binding.
 *
 * Cody-specific pen-tests:
 *   PT-ADAPT-CODY-1: empty packBinding[] throws UnboundDataError pre-call
 *   PT-ADAPT-CODY-2: auth tokens / API keys never appear in audit entries
 *   PT-ADAPT-CODY-3: malformed config (invalid deploymentMode) throws at construction
 *   PT-ADAPT-CODY-4: embeddingsIndex blocked in free-pro mode
 *
 * Spec: P3-SUB-IDE-06
 *
 * @connexum/ai-governance
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.CodyAdapter = exports.CODY_BAA_ELIGIBLE_MODES = void 0;
const base_1 = require("./base");
const index_1 = require("./index");
const shared_1 = require("./shared");
exports.CODY_BAA_ELIGIBLE_MODES = [
    'self-hosted', // customer controls data residency
    'sourcegraph-cloud', // eligible with active Sourcegraph BAA
];
// ---------------------------------------------------------------------------
// Secret patterns
// ---------------------------------------------------------------------------
const CODY_SECRET_PATTERNS = [
    { name: 'AWS access key', re: /AKIA[0-9A-Z]{16}/ },
    { name: 'GitHub PAT', re: /ghp_[0-9A-Za-z]{36}|github_pat_[0-9A-Za-z_]{82}/ },
    { name: 'Anthropic API key', re: /sk-ant-[0-9A-Za-z\-_]{20,}/ },
    { name: 'OpenAI API key', re: /sk-[0-9A-Za-z]{20,}/ },
    { name: 'Private key header', re: /-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----/ },
    { name: 'Generic secret env var', re: /(SECRET|PASSWORD|PASSWD|API_KEY|PRIVATE_KEY)\s*=\s*["']?\S{8,}["']?/i },
    { name: 'Bearer token', re: /Authorization:\s*Bearer\s+[0-9A-Za-z\-._~+/]{20,}/i },
    { name: 'Database URL with creds', re: /(postgres|mysql|mongodb|redis):\/\/[^:]+:[^@]+@/ },
    { name: 'Sourcegraph access token', re: /sgp_[0-9A-Za-z]{20,}/ },
];
// ---------------------------------------------------------------------------
// PHI indicators
// ---------------------------------------------------------------------------
const CODY_PHI_INDICATORS = [
    /\bpatient[_\s]?(id|name|dob|ssn|mrn|record)\b/i,
    /\bdiagnos(is|es)\b/i,
    /\bprescription\b/i,
    /\bmedical[_\s]?record\b/i,
    /\bhealth[_\s]?plan\b/i,
    /\bssn\b/,
    /\bdate[_\s]?of[_\s]?birth\b/i,
    /\btreatment[_\s]?plan\b/i,
    /\bhipaa\b/i,
    /\bphi\b/,
];
// ---------------------------------------------------------------------------
// CodyAdapter
// ---------------------------------------------------------------------------
class CodyAdapter extends base_1.BaseIdeAdapter {
    adapterId = 'sourcegraph-cody';
    ideId = 'sourcegraph-cody';
    codyConfig = null;
    // ---------------------------------------------------------------------------
    // Lifecycle
    // ---------------------------------------------------------------------------
    initialize(config) {
        const codyConfig = config;
        // PT-ADAPT-CODY-3: validate deploymentMode at construction time.
        const validModes = ['sourcegraph-cloud', 'self-hosted', 'free-pro'];
        if (!codyConfig.deploymentMode || !validModes.includes(codyConfig.deploymentMode)) {
            throw new index_1.IdeSessionError({
                message: `CodyAdapter: invalid or missing deploymentMode '${codyConfig.deploymentMode}'. ` +
                    `Must be one of: ${validModes.join(', ')}. ` +
                    `Deployment mode is required to enforce data-residency bounds.`,
                ideId: this.ideId,
                adapterId: this.adapterId,
            });
        }
        super.initialize(config);
        this.codyConfig = codyConfig;
        this.emit((0, shared_1.buildIdeAuditEvent)('ide:session-open', {
            ideId: this.ideId,
            adapterId: this.adapterId,
            detail: `CodyAdapter initialized. ` +
                `deploymentMode: ${codyConfig.deploymentMode}. ` +
                `BAA eligible: ${exports.CODY_BAA_ELIGIBLE_MODES.includes(codyConfig.deploymentMode)}.`,
            data: {
                deploymentMode: codyConfig.deploymentMode,
                baaEligible: exports.CODY_BAA_ELIGIBLE_MODES.includes(codyConfig.deploymentMode),
            },
        }));
    }
    // ---------------------------------------------------------------------------
    // IdeAdapter: resolveUserIdentity
    //
    // Cody identity is provided by the Sourcegraph authentication layer
    // (Sourcegraph Account, GitHub OAuth, SAML SSO, or LDAP in self-hosted).
    // The shim receives the resolved userId from the integration layer.
    // ---------------------------------------------------------------------------
    async resolveUserIdentity(session) {
        this.ensureInitialized();
        const fromSession = session.identity?.userId?.trim();
        if (fromSession) {
            const identity = {
                userId: fromSession,
                email: session.identity?.email,
                orgId: session.identity?.orgId,
                githubLogin: session.identity?.githubLogin,
                attributionMethod: 'cursor-composer',
            };
            this.emitIdentityResolved(identity.userId, session.clientSessionId);
            return identity;
        }
        const allowAnon = this.config?.allowAnonymousIdeSessions === true;
        if (allowAnon) {
            const anon = { userId: 'anonymous', attributionMethod: 'cursor-composer' };
            this.emitIdentityResolved(anon.userId, session.clientSessionId);
            return anon;
        }
        this.emitIdentityUnresolved(session.clientSessionId, 'CodyAdapter: no user identity in session context.');
        throw new index_1.IdeAttributionError({
            message: 'CodyAdapter: cannot resolve user identity. Provide a userId in ' +
                'session.identity.userId (the Sourcegraph account login or SSO ID), or set ' +
                'allowAnonymousIdeSessions: true in adapter config.',
            ideId: this.ideId,
            adapterId: this.adapterId,
        });
    }
    // ---------------------------------------------------------------------------
    // IdeAdapter: interceptToolInvocation
    //
    // Normalised tool surface names:
    //   Standard:
    //   'cody:autocomplete-requested'       -> autocomplete gate
    //   'cody:chat-requested'               -> chat gate
    //   'cody:suggestion-accepted'          -> audit event
    //   'cody:suggestion-rejected'          -> audit event
    //
    //   Cody-specific:
    //   'cody:code-search-requested'        -> codeSearchRequested gate
    //   'cody:context-fetch-requested'      -> contextFetchRequested gate
    //   'cody:commit-message-requested'     -> commitMessageRequested gate
    //   'cody:recipe-execution-requested'   -> recipeExecutionRequested gate
    //   'cody:embeddings-index-requested'   -> embeddingsIndexRequested gate
    // ---------------------------------------------------------------------------
    async interceptToolInvocation(ctx) {
        this.ensureInitialized();
        switch (ctx.tool) {
            case 'cody:autocomplete-requested':
                return this._gateAutocompleteRequest(ctx);
            case 'cody:chat-requested':
                return this._gateChatRequest(ctx);
            case 'cody:suggestion-accepted':
                return this._auditSuggestionAccepted(ctx);
            case 'cody:suggestion-rejected':
                return this._auditSuggestionRejected(ctx);
            case 'cody:code-search-requested':
                return this._gateCodeSearchRequest(ctx);
            case 'cody:context-fetch-requested':
                return this._gateContextFetchRequest(ctx);
            case 'cody:commit-message-requested':
                return this._gateCommitMessageRequest(ctx);
            case 'cody:recipe-execution-requested':
                return this._gateRecipeExecutionRequest(ctx);
            case 'cody:embeddings-index-requested':
                return this._gateEmbeddingsIndexRequest(ctx);
            default:
                this.emit((0, shared_1.buildIdeAuditEvent)('ide:tool-invocation-approved', {
                    ideId: this.ideId,
                    adapterId: this.adapterId,
                    sessionId: ctx.sessionHandle.sessionId,
                    initiatingUserId: ctx.initiatingUser.userId,
                    tool: ctx.tool,
                    detail: `CodyAdapter: unknown surface '${ctx.tool}' -- allowed with audit.`,
                }));
                return this.routeToolInvocation(ctx, {
                    decision: 'ALLOW',
                    reason: `Unknown Cody surface '${ctx.tool}' permitted with audit.`,
                });
        }
    }
    // ---------------------------------------------------------------------------
    // Hook: autocompleteRequested
    // ---------------------------------------------------------------------------
    async _gateAutocompleteRequest(ctx) {
        const surroundingContext = String(ctx.input['surroundingContext'] ?? ctx.input['context'] ?? '');
        const filePath = String(ctx.input['filePath'] ?? ctx.input['file_path'] ?? '');
        const secretMatch = this._scanForSecrets(surroundingContext);
        if (secretMatch) {
            return this.routeToolInvocation(ctx, {
                decision: 'DENY',
                reason: `Autocomplete blocked: secret pattern '${secretMatch}' in surrounding context (${filePath}).`,
                metadata: { filePath, secretPattern: secretMatch },
            });
        }
        if (this._isHipaaBinding()) {
            const phiMatch = this._scanForPhiIndicators(surroundingContext);
            if (phiMatch) {
                const tier = this.codyConfig?.hipaaReviewerTier ?? 'T2_LEAD';
                return this.routeToolInvocation(ctx, {
                    decision: 'REQUIRE_APPROVAL',
                    reason: `Autocomplete routed for HIPAA review: PHI indicator '${phiMatch}' in context. Tier: ${tier}.`,
                    metadata: { filePath, phiIndicator: phiMatch, tier },
                });
            }
        }
        const deployDecision = await this._checkDeploymentModeCompliance(ctx);
        if (deployDecision)
            return deployDecision;
        return this.routeToolInvocation(ctx, {
            decision: 'ALLOW',
            reason: `Autocomplete request permitted by CodyAdapter.`,
            metadata: { filePath, deploymentMode: this.codyConfig?.deploymentMode },
        });
    }
    // ---------------------------------------------------------------------------
    // Hook: chatRequested
    // ---------------------------------------------------------------------------
    async _gateChatRequest(ctx) {
        const promptText = String(ctx.input['promptText'] ?? ctx.input['prompt'] ?? '');
        const repoContext = ctx.input['repoContext'] ?? [];
        const secretMatch = this._scanForSecrets(promptText);
        if (secretMatch) {
            return this.routeToolInvocation(ctx, {
                decision: 'DENY',
                reason: `Chat request blocked: secret pattern '${secretMatch}' in prompt.`,
                metadata: { secretPattern: secretMatch },
            });
        }
        if (this._isHipaaBinding()) {
            const phiMatch = this._scanForPhiIndicators(promptText);
            if (phiMatch) {
                const tier = this.codyConfig?.hipaaReviewerTier ?? 'T2_LEAD';
                return this.routeToolInvocation(ctx, {
                    decision: 'REQUIRE_APPROVAL',
                    reason: `Chat request routed for HIPAA review: PHI indicator '${phiMatch}'. Tier: ${tier}.`,
                    metadata: { phiIndicator: phiMatch, tier },
                });
            }
        }
        const deployDecision = await this._checkDeploymentModeCompliance(ctx);
        if (deployDecision)
            return deployDecision;
        return this.routeToolInvocation(ctx, {
            decision: 'ALLOW',
            reason: 'Chat request permitted by CodyAdapter.',
            metadata: {
                attachedRepos: repoContext.length,
                deploymentMode: this.codyConfig?.deploymentMode,
            },
        });
    }
    // ---------------------------------------------------------------------------
    // Hook: suggestionAccepted
    // ---------------------------------------------------------------------------
    async _auditSuggestionAccepted(ctx) {
        const targetFilePath = String(ctx.input['targetFilePath'] ?? '');
        const completionId = String(ctx.input['completionId'] ?? '');
        this.emit((0, shared_1.buildIdeAuditEvent)('ide:tool-invocation-approved', {
            ideId: this.ideId,
            adapterId: this.adapterId,
            sessionId: ctx.sessionHandle.sessionId,
            initiatingUserId: ctx.initiatingUser.userId,
            tool: ctx.tool,
            detail: `Developer accepted a Cody suggestion. Target: ${targetFilePath}. CompletionId: ${completionId || 'n/a'}.`,
            data: { targetFilePath, completionId },
        }));
        return this.routeToolInvocation(ctx, {
            decision: 'ALLOW',
            reason: `Suggestion acceptance audited for '${targetFilePath}'.`,
            metadata: { targetFilePath, completionId },
        });
    }
    // ---------------------------------------------------------------------------
    // Hook: suggestionRejected
    // ---------------------------------------------------------------------------
    async _auditSuggestionRejected(ctx) {
        const reason = String(ctx.input['reason'] ?? 'not-specified');
        const completionId = String(ctx.input['completionId'] ?? '');
        this.emit((0, shared_1.buildIdeAuditEvent)('ide:tool-invocation-approved', {
            ideId: this.ideId,
            adapterId: this.adapterId,
            sessionId: ctx.sessionHandle.sessionId,
            initiatingUserId: ctx.initiatingUser.userId,
            tool: ctx.tool,
            detail: `Developer rejected a Cody suggestion. Reason: ${reason}. CompletionId: ${completionId || 'n/a'}.`,
            data: { reason, completionId },
        }));
        return this.routeToolInvocation(ctx, {
            decision: 'ALLOW',
            reason: `Suggestion rejection audited. Reason: ${reason}.`,
        });
    }
    // ---------------------------------------------------------------------------
    // Cody-specific Hook: codeSearchRequested
    //
    // Sourcegraph's code-search is a distinct AI surface: the query expresses
    // intent over repositories and the results may contain secrets. The pre-call
    // gate checks the query for embedded secrets or PHI before it is executed.
    // The result context is governed by contextFetchRequested.
    // ---------------------------------------------------------------------------
    async codeSearchRequested(ctx) {
        this.ensureInitialized();
        const { query, repos } = ctx;
        const secretMatch = this._scanForSecrets(query);
        if (secretMatch) {
            const decision = {
                decision: 'DENY',
                reason: `Code search blocked: secret pattern '${secretMatch}' found in query. ` +
                    `Do not embed credentials or tokens in code-search queries.`,
                metadata: { secretPattern: secretMatch },
            };
            this.emit((0, shared_1.buildIdeAuditEvent)('ide:tool-invocation-denied', {
                ideId: this.ideId,
                adapterId: this.adapterId,
                tool: 'cody:code-search-requested',
                sessionId: ctx.sessionId,
                detail: decision.reason,
            }));
            return decision;
        }
        if (this._isHipaaBinding()) {
            const phiMatch = this._scanForPhiIndicators(query);
            if (phiMatch) {
                const tier = this.codyConfig?.hipaaReviewerTier ?? 'T2_LEAD';
                const decision = {
                    decision: 'REQUIRE_APPROVAL',
                    reason: `Code search routed for HIPAA review: PHI indicator '${phiMatch}' in query. ` +
                        `Reviewer tier: ${tier}. Ensure search results containing PHI are tagged before use.`,
                    metadata: { phiIndicator: phiMatch, tier },
                };
                this.emit((0, shared_1.buildIdeAuditEvent)('ide:tool-invocation-require-approval', {
                    ideId: this.ideId,
                    adapterId: this.adapterId,
                    tool: 'cody:code-search-requested',
                    sessionId: ctx.sessionId,
                    detail: decision.reason,
                    data: { phiIndicator: phiMatch, repos },
                }));
                return decision;
            }
        }
        this.emit((0, shared_1.buildIdeAuditEvent)('ide:tool-invocation-approved', {
            ideId: this.ideId,
            adapterId: this.adapterId,
            tool: 'cody:code-search-requested',
            sessionId: ctx.sessionId,
            detail: `Code search permitted. Query length: ${query.length}. Repos: ${repos?.join(', ') ?? 'all'}.`,
            data: { repos, queryLength: query.length },
        }));
        return {
            decision: 'ALLOW',
            reason: `Code search permitted by CodyAdapter.`,
            metadata: { repos, deploymentMode: this.codyConfig?.deploymentMode },
        };
    }
    // ---------------------------------------------------------------------------
    // Cody-specific Hook: contextFetchRequested
    //
    // Cody's context-fetch surface retrieves files and symbols from the
    // Sourcegraph index and injects them into the AI prompt window. This gate
    // scans the file paths for sensitive patterns (private keys, .env files, etc.)
    // before the fetch is allowed to proceed.
    // ---------------------------------------------------------------------------
    async contextFetchRequested(ctx) {
        this.ensureInitialized();
        const { filePaths, symbols } = ctx;
        // Check for sensitive file paths
        const sensitivePaths = filePaths.filter((p) => this._isSensitivePath(p));
        if (sensitivePaths.length > 0) {
            const decision = {
                decision: 'DENY',
                reason: `Context fetch blocked: ${sensitivePaths.length} sensitive path(s) detected: ` +
                    `[${sensitivePaths.slice(0, 3).join(', ')}${sensitivePaths.length > 3 ? ', ...' : ''}]. ` +
                    `Sensitive files (secrets, private keys, .env) must not be injected into AI context windows.`,
                metadata: { sensitivePaths: sensitivePaths.slice(0, 10) },
            };
            this.emit((0, shared_1.buildIdeAuditEvent)('ide:tool-invocation-denied', {
                ideId: this.ideId,
                adapterId: this.adapterId,
                tool: 'cody:context-fetch-requested',
                sessionId: ctx.sessionId,
                detail: decision.reason,
                data: { sensitivePaths: sensitivePaths.slice(0, 5) },
            }));
            return decision;
        }
        this.emit((0, shared_1.buildIdeAuditEvent)('ide:tool-invocation-approved', {
            ideId: this.ideId,
            adapterId: this.adapterId,
            tool: 'cody:context-fetch-requested',
            sessionId: ctx.sessionId,
            detail: `Context fetch permitted. Paths: ${filePaths.length}. Symbols: ${symbols?.length ?? 0}.`,
            data: { filePathCount: filePaths.length, symbolCount: symbols?.length ?? 0 },
        }));
        return {
            decision: 'ALLOW',
            reason: `Context fetch permitted by CodyAdapter. ${filePaths.length} path(s) cleared.`,
            metadata: { filePathCount: filePaths.length, deploymentMode: this.codyConfig?.deploymentMode },
        };
    }
    // ---------------------------------------------------------------------------
    // Cody-specific Hook: commitMessageRequested
    //
    // Cody can generate commit messages from diffs. This gate classifies the
    // diff for secret leaks BEFORE the message generation prompt is sent.
    // ---------------------------------------------------------------------------
    async commitMessageRequested(ctx) {
        this.ensureInitialized();
        const secretMatch = this._scanDiffForSecrets(ctx.diff);
        if (secretMatch) {
            const decision = {
                decision: 'DENY',
                reason: `Commit message generation blocked: secret pattern '${secretMatch.pattern}' ` +
                    `detected in diff at line ${secretMatch.line}. ` +
                    `Remove the secret from staged changes before generating a commit message. ` +
                    `Branch: ${ctx.branchName ?? 'unknown'}.`,
                metadata: {
                    branchName: ctx.branchName,
                    secretPattern: secretMatch.pattern,
                    diffLine: secretMatch.line,
                },
            };
            this.emit((0, shared_1.buildIdeAuditEvent)('ide:tool-invocation-denied', {
                ideId: this.ideId,
                adapterId: this.adapterId,
                tool: 'cody:commit-message-requested',
                sessionId: ctx.sessionId,
                detail: decision.reason,
                data: { branchName: ctx.branchName, secretPattern: secretMatch.pattern },
            }));
            return decision;
        }
        this.emit((0, shared_1.buildIdeAuditEvent)('ide:tool-invocation-approved', {
            ideId: this.ideId,
            adapterId: this.adapterId,
            tool: 'cody:commit-message-requested',
            sessionId: ctx.sessionId,
            detail: `Commit message generation permitted. Branch: ${ctx.branchName ?? 'unknown'}. Diff passed secret scan.`,
            data: { branchName: ctx.branchName },
        }));
        return {
            decision: 'ALLOW',
            reason: `Commit message generation permitted. Diff passed secret scan.`,
            metadata: { branchName: ctx.branchName },
        };
    }
    // ---------------------------------------------------------------------------
    // Cody-specific Hook: recipeExecutionRequested
    //
    // Cody recipes are structured AI workflows ("Explain code", "Generate unit
    // test", "Find code smells", etc.). Recipe execution is treated as a bounded
    // autonomous operation. Under a regulated pack binding, recipes that touch
    // PHI-indicator symbols are routed to a reviewer.
    // ---------------------------------------------------------------------------
    async recipeExecutionRequested(ctx) {
        this.ensureInitialized();
        const { recipeId, targetFilePath } = ctx;
        const recipeArgsText = JSON.stringify(ctx.recipeArgs ?? {});
        const secretMatch = this._scanForSecrets(recipeArgsText);
        if (secretMatch) {
            const decision = {
                decision: 'DENY',
                reason: `Recipe execution blocked: secret pattern '${secretMatch}' found in recipe args ` +
                    `for recipe '${recipeId}'. Remove credentials from recipe arguments.`,
                metadata: { recipeId, secretPattern: secretMatch, targetFilePath },
            };
            this.emit((0, shared_1.buildIdeAuditEvent)('ide:tool-invocation-denied', {
                ideId: this.ideId,
                adapterId: this.adapterId,
                tool: 'cody:recipe-execution-requested',
                sessionId: ctx.sessionId,
                detail: decision.reason,
                data: { recipeId, targetFilePath },
            }));
            return decision;
        }
        if (this._isHipaaBinding()) {
            const phiMatch = this._scanForPhiIndicators(recipeArgsText);
            if (phiMatch) {
                const tier = this.codyConfig?.recipeReviewerTier ?? 'T2_LEAD';
                const decision = {
                    decision: 'REQUIRE_APPROVAL',
                    reason: `Recipe execution for '${recipeId}' routed for HIPAA review: ` +
                        `PHI indicator '${phiMatch}' in recipe arguments. ` +
                        `Ensure recipe output does not contain identifiable health information. Tier: ${tier}.`,
                    metadata: { recipeId, phiIndicator: phiMatch, tier, targetFilePath },
                };
                this.emit((0, shared_1.buildIdeAuditEvent)('ide:tool-invocation-require-approval', {
                    ideId: this.ideId,
                    adapterId: this.adapterId,
                    tool: 'cody:recipe-execution-requested',
                    sessionId: ctx.sessionId,
                    detail: decision.reason,
                    data: { recipeId, phiIndicator: phiMatch, targetFilePath },
                }));
                return decision;
            }
        }
        this.emit((0, shared_1.buildIdeAuditEvent)('ide:tool-invocation-approved', {
            ideId: this.ideId,
            adapterId: this.adapterId,
            tool: 'cody:recipe-execution-requested',
            sessionId: ctx.sessionId,
            detail: `Recipe '${recipeId}' permitted. Target: ${targetFilePath ?? 'n/a'}.`,
            data: { recipeId, targetFilePath },
        }));
        return {
            decision: 'ALLOW',
            reason: `Recipe '${recipeId}' execution permitted by CodyAdapter.`,
            metadata: { recipeId, targetFilePath, deploymentMode: this.codyConfig?.deploymentMode },
        };
    }
    // ---------------------------------------------------------------------------
    // Cody-specific Hook: embeddingsIndexRequested (PT-ADAPT-CODY-4)
    //
    // Embeddings index build is an enterprise-only feature. free-pro mode does
    // NOT support embeddings indexing. The adapter blocks this surface in
    // free-pro mode with an explicit error message.
    //
    // Under HIPAA binding + self-hosted: permitted (customer controls data).
    // Under HIPAA binding + sourcegraph-cloud: permitted with warning (BAA req).
    // Under HIPAA binding + free-pro: DENY (not BAA-eligible + feature unsupported).
    // ---------------------------------------------------------------------------
    async embeddingsIndexRequested(ctx) {
        this.ensureInitialized();
        const mode = this.codyConfig?.deploymentMode ?? 'free-pro';
        // PT-ADAPT-CODY-4: embeddings index blocked in free-pro mode.
        if (mode === 'free-pro') {
            const decision = {
                decision: 'DENY',
                reason: `Embeddings index operation blocked: CodyAdapter is configured in 'free-pro' mode. ` +
                    `Embeddings indexing is only available in 'sourcegraph-cloud' and 'self-hosted' ` +
                    `deployment modes. Upgrade to a Sourcegraph Enterprise instance to use this feature.`,
                metadata: { deploymentMode: mode, repos: ctx.repos },
            };
            this.emit((0, shared_1.buildIdeAuditEvent)('ide:tool-invocation-denied', {
                ideId: this.ideId,
                adapterId: this.adapterId,
                tool: 'cody:embeddings-index-requested',
                sessionId: ctx.sessionId,
                detail: decision.reason,
                data: { deploymentMode: mode, repoCount: ctx.repos.length },
            }));
            return decision;
        }
        // HIPAA binding + free-pro already blocked above.
        // HIPAA binding + non-BAA-eligible mode (should not happen given above, but guard defensively).
        if (this._isHipaaBinding() && !exports.CODY_BAA_ELIGIBLE_MODES.includes(mode)) {
            const decision = {
                decision: 'DENY',
                reason: `Embeddings index blocked: deployment mode '${mode}' is not BAA-eligible ` +
                    `for HIPAA pack binding '${this.config?.packId}'. ` +
                    `Use 'self-hosted' or 'sourcegraph-cloud' (with active Sourcegraph BAA).`,
                metadata: { deploymentMode: mode, repos: ctx.repos },
            };
            this.emit((0, shared_1.buildIdeAuditEvent)('ide:tool-invocation-denied', {
                ideId: this.ideId,
                adapterId: this.adapterId,
                tool: 'cody:embeddings-index-requested',
                sessionId: ctx.sessionId,
                detail: decision.reason,
                data: { deploymentMode: mode, repos: ctx.repos },
            }));
            return decision;
        }
        this.emit((0, shared_1.buildIdeAuditEvent)('ide:tool-invocation-approved', {
            ideId: this.ideId,
            adapterId: this.adapterId,
            tool: 'cody:embeddings-index-requested',
            sessionId: ctx.sessionId,
            detail: `Embeddings index permitted for ${ctx.repos.length} repo(s) in ${mode} mode.`,
            data: { repos: ctx.repos, deploymentMode: mode },
        }));
        return {
            decision: 'ALLOW',
            reason: `Embeddings index permitted for ${ctx.repos.length} repo(s). Mode: ${mode}.`,
            metadata: { repos: ctx.repos, deploymentMode: mode },
        };
    }
    // ---------------------------------------------------------------------------
    // interceptToolInvocation dispatch helpers for Cody-specific surfaces
    // ---------------------------------------------------------------------------
    async _gateCodeSearchRequest(ctx) {
        return this.codeSearchRequested({
            query: String(ctx.input['query'] ?? ''),
            repos: ctx.input['repos'],
            maxResults: ctx.input['maxResults'],
            sessionId: ctx.sessionHandle.sessionId,
        });
    }
    async _gateContextFetchRequest(ctx) {
        return this.contextFetchRequested({
            filePaths: ctx.input['filePaths'] ?? [],
            symbols: ctx.input['symbols'],
            sessionId: ctx.sessionHandle.sessionId,
        });
    }
    async _gateCommitMessageRequest(ctx) {
        return this.commitMessageRequested({
            diff: String(ctx.input['diff'] ?? ''),
            branchName: ctx.input['branchName'],
            sessionId: ctx.sessionHandle.sessionId,
        });
    }
    async _gateRecipeExecutionRequest(ctx) {
        return this.recipeExecutionRequested({
            recipeId: String(ctx.input['recipeId'] ?? ctx.input['recipe'] ?? ''),
            recipeArgs: ctx.input['recipeArgs'],
            targetFilePath: ctx.input['targetFilePath'],
            sessionId: ctx.sessionHandle.sessionId,
        });
    }
    async _gateEmbeddingsIndexRequest(ctx) {
        return this.embeddingsIndexRequested({
            repos: ctx.input['repos'] ?? [],
            sessionId: ctx.sessionHandle.sessionId,
        });
    }
    // ---------------------------------------------------------------------------
    // Capabilities
    // ---------------------------------------------------------------------------
    getIdeCapabilities() {
        return {
            runtimeHooks: true,
            prBoundary: false,
            userIdentity: true,
            reviewerRouting: true,
            integrationMode: 'cody-normalised-event-shim',
        };
    }
    // ---------------------------------------------------------------------------
    // Error translation
    // ---------------------------------------------------------------------------
    translateError(raw) {
        if (raw instanceof index_1.IdeAttributionError)
            return raw;
        if (raw instanceof index_1.IdeUnsupportedError)
            return raw;
        return super.translateError(raw);
    }
    // ---------------------------------------------------------------------------
    // Public accessors for diagnostics / tests
    // ---------------------------------------------------------------------------
    getDeploymentMode() {
        return this.codyConfig?.deploymentMode ?? 'free-pro';
    }
    // ---------------------------------------------------------------------------
    // Helpers
    // ---------------------------------------------------------------------------
    _scanForSecrets(text) {
        if (!text)
            return null;
        for (const { name, re } of CODY_SECRET_PATTERNS) {
            if (re.test(text))
                return name;
        }
        return null;
    }
    _scanForPhiIndicators(text) {
        if (!text)
            return null;
        for (const re of CODY_PHI_INDICATORS) {
            if (re.test(text))
                return re.source;
        }
        return null;
    }
    _isHipaaBinding() {
        const packId = this.config?.packId ?? '';
        return packId === 'hipaa' || packId.startsWith('hipaa');
    }
    /**
     * Scan a unified diff for secrets. Returns the first finding or null.
     * Only scans added lines (lines starting with '+').
     */
    _scanDiffForSecrets(diff) {
        const lines = diff.split('\n');
        for (let i = 0; i < lines.length; i++) {
            const line = lines[i];
            if (!line || !line.startsWith('+') || line.startsWith('+++'))
                continue;
            const content = line.slice(1);
            for (const { name, re } of CODY_SECRET_PATTERNS) {
                if (re.test(content)) {
                    return { line: i + 1, pattern: name };
                }
            }
        }
        return null;
    }
    /**
     * Returns true if the file path matches a sensitive pattern.
     * Sensitive paths must not be injected into Cody context windows.
     */
    _isSensitivePath(filePath) {
        const SENSITIVE = [
            /^\/etc\//,
            /^\/root\//,
            /(^|\/)\.env$/,
            /\.env\./,
            /secrets?\//i,
            /private_?key/i,
            /\.pem$/i,
            /\.key$/i,
            /id_rsa/i,
            /id_ed25519/i,
            /\.pfx$/i,
            /\.p12$/i,
        ];
        return SENSITIVE.some((re) => re.test(filePath));
    }
    /**
     * Check deployment-mode-level compliance for the active pack binding.
     * Returns a DENY decision if free-pro is used under a regulated pack binding
     * (free-pro has no BAA eligibility), or null if no issue.
     */
    async _checkDeploymentModeCompliance(ctx) {
        if (!this._isHipaaBinding())
            return null;
        const mode = this.codyConfig?.deploymentMode ?? 'free-pro';
        if (!exports.CODY_BAA_ELIGIBLE_MODES.includes(mode)) {
            return this.routeToolInvocation(ctx, {
                decision: 'DENY',
                reason: `CodyAdapter: deployment mode '${mode}' is not BAA-eligible for HIPAA pack ` +
                    `binding '${this.config?.packId}'. ` +
                    `Switch to 'self-hosted' or 'sourcegraph-cloud' and ensure an active ` +
                    `Sourcegraph BAA is in place before using Cody on HIPAA-regulated code.`,
                metadata: {
                    deploymentMode: mode,
                    hipaaPackId: this.config?.packId,
                    eligibleModes: exports.CODY_BAA_ELIGIBLE_MODES,
                },
            });
        }
        return null;
    }
}
exports.CodyAdapter = CodyAdapter;
//# sourceMappingURL=cody.js.map