/**
 * GitHubCopilotIdeAdapter -- GitHub Copilot classic adapter.
 *
 * Governs GitHub Copilot classic: the autocomplete + Copilot Chat surface
 * inside VS Code, JetBrains IDEs, Vim, and Visual Studio. Distinct from
 * Copilot Workspace, which is the agentic PR-level surface (see
 * src/ide-adapters/copilot-workspace.ts).
 *
 * Hook surfaces:
 *   completionRequested(file, cursor, context) -- pre-completion gate.
 *     Classifier checks for secret patterns, PHI, and trade-secret markers
 *     in the surrounding code context BEFORE the request leaves the IDE.
 *   completionReceived(completion, context) -- post-completion ingress.
 *     Scans the returned completion for secret material. GitHub Copilot has
 *     historically leaked API keys from training data.
 *   chatRequested(prompt, context) -- pre-chat gate. Same classifier pass on
 *     the prompt body and attached files.
 *   chatReceived(response) -- post-chat ingress classification.
 *   suggestionAccepted(suggestion, targetFile) -- audit event on acceptance,
 *     links the accepted code to its originating completion.
 *   suggestionRejected(suggestion, reason) -- optional audit event.
 *
 * Copilot-specific considerations:
 *   - Copilot Content Exclusion is Microsoft's own policy layer. This
 *     adapter surfaces it as a pack-level binding configuration flag --
 *     it is a complement, not a replacement.
 *   - Enterprise deployment flavour (Business vs Enterprise) is read from
 *     config at initialize time and applied to provider-compliance decisions.
 *   - Public-code filter mode is surfaced as an auditable config flag.
 *   - Editor-specific shims (VS Code, JetBrains, Vim, Visual Studio) are
 *     DEFERRED: DEFERRED-COPILOT-EDITOR-SHIMS. This adapter is editor-
 *     agnostic and receives normalised events from a thin shim per editor.
 *
 * Spec: P3-SUB-IDE-02
 *
 * @connexum/ai-governance
 */
import { BaseIdeAdapter } from './base.js';
import { IdeAttributionError, IdeUnsupportedError, } from './index.js';
import { buildIdeAuditEvent } from './shared.js';
/**
 * Data-retention commitment per Copilot flavour.
 *
 * Copilot Individual: prompts and completions may be used for model training
 *   unless telemetry opt-out is active. Not suitable for any regulated data.
 * Copilot Business: prompt retention is disabled by default.
 * Copilot Enterprise: prompt retention off, content exclusions configurable,
 *   and enterprise-level data-handling commitments apply.
 *
 * Reference: https://docs.github.com/en/copilot/github-copilot-enterprise
 */
export const COPILOT_DATA_RETENTION = {
    individual: 'prompts-may-be-retained-for-training',
    business: 'prompt-retention-disabled-by-default',
    enterprise: 'enterprise-level-data-commitments',
};
/**
 * Flavours that carry a Microsoft / GitHub data-processing agreement
 * suitable for HIPAA use (with explicit Business Associate Agreement in place).
 *
 * Individual flavour NEVER qualifies because GitHub has no BAA for it.
 */
export const COPILOT_BAA_ELIGIBLE_FLAVOURS = [
    'business',
    'enterprise',
];
// ---------------------------------------------------------------------------
// GitHub Copilot secret patterns (supplement shared patterns)
//
// Copilot has historically surfaced training-data secrets. These patterns
// are tuned to the categories most frequently observed in Copilot completions.
// ---------------------------------------------------------------------------
const COPILOT_COMPLETION_SECRET_PATTERNS = [
    { name: 'AWS access key', re: /AKIA[0-9A-Z]{16}/ },
    { name: 'GitHub PAT (classic)', re: /ghp_[0-9A-Za-z]{36}/ },
    { name: 'GitHub fine-grained PAT', re: /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: 'OAuth bearer token', re: /Bearer\s+[0-9A-Za-z\-._~+/]{20,}/i },
    { name: 'Generic secret env var', re: /(SECRET|PASSWORD|API_KEY|PRIVATE_KEY)\s*=\s*["']?\S{8,}["']?/i },
    { name: 'Database URL with creds', re: /(postgres|mysql|mongodb|redis):\/\/[^:]+:[^@]+@/ },
    { name: 'Private key header', re: /-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----/ },
    { name: 'Azure SAS token', re: /sig=[0-9A-Za-z%+/]{20,}/ },
    { name: 'Stripe secret key', re: /sk_live_[0-9A-Za-z]{24,}/ },
];
/**
 * PHI proximity indicators in code context.
 * These do NOT classify the context as confirmed PHI -- they signal elevated
 * risk that the surrounding content may include protected health information.
 */
const PHI_CONTEXT_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,
    /\bsocial[_\s]?security\b/i,
    /\bssn\b/,
    /\bdate[_\s]?of[_\s]?birth\b/i,
    /\bdob\b/,
    /\btreatment[_\s]?plan\b/i,
    /\bhipaa\b/i,
    /\bphi\b/,
];
// ---------------------------------------------------------------------------
// GitHubCopilotIdeAdapter
// ---------------------------------------------------------------------------
export class GitHubCopilotIdeAdapter extends BaseIdeAdapter {
    adapterId = 'github-copilot';
    ideId = 'github-copilot';
    copilotConfig = null;
    // ---------------------------------------------------------------------------
    // Lifecycle
    // ---------------------------------------------------------------------------
    initialize(config) {
        super.initialize(config);
        this.copilotConfig = config;
        const flavour = this.copilotConfig.copilotFlavour ?? 'business';
        // Emit initialisation event noting the flavour and its retention posture
        this.emit(buildIdeAuditEvent('ide:session-open', {
            ideId: this.ideId,
            adapterId: this.adapterId,
            detail: `GitHubCopilotIdeAdapter initialized. ` +
                `Flavour: ${flavour}. ` +
                `Retention posture: ${COPILOT_DATA_RETENTION[flavour]}. ` +
                `Public-code filter: ${this.copilotConfig.publicCodeFilter ?? 'unknown'}.`,
            data: {
                flavour,
                retentionPosture: COPILOT_DATA_RETENTION[flavour],
                publicCodeFilter: this.copilotConfig.publicCodeFilter ?? 'unknown',
            },
        }));
    }
    // ---------------------------------------------------------------------------
    // IdeAdapter: resolveUserIdentity
    //
    // GitHub Copilot classic identity comes from the GitHub user authenticated
    // to the IDE via the Copilot plugin. The shim provides this as the userId
    // (typically the GitHub login). If absent and anon sessions are not
    // permitted, fail-closed per governance rule.
    // ---------------------------------------------------------------------------
    async resolveUserIdentity(session) {
        this.ensureInitialized();
        const fromSession = session.identity?.userId?.trim();
        const githubLogin = session.identity?.githubLogin?.trim();
        if (fromSession || githubLogin) {
            const identity = {
                userId: fromSession ?? githubLogin,
                githubLogin: githubLogin,
                email: session.identity?.email,
                orgId: session.identity?.orgId,
                attributionMethod: 'github-webhook',
            };
            this.emitIdentityResolved(identity.userId, session.clientSessionId);
            return identity;
        }
        const allowAnon = this.config?.allowAnonymousIdeSessions === true;
        if (allowAnon) {
            const anon = { userId: 'anonymous', attributionMethod: 'github-webhook' };
            this.emitIdentityResolved(anon.userId, session.clientSessionId);
            return anon;
        }
        this.emitIdentityUnresolved(session.clientSessionId, 'GitHubCopilotIdeAdapter: no user identity in session context.');
        throw new IdeAttributionError({
            message: 'GitHubCopilotIdeAdapter: cannot resolve user identity. Provide a ' +
                'GitHub login in session.identity.githubLogin or session.identity.userId, ' +
                'or set allowAnonymousIdeSessions: true in adapter config.',
            ideId: this.ideId,
            adapterId: this.adapterId,
        });
    }
    // ---------------------------------------------------------------------------
    // IdeAdapter: interceptToolInvocation
    //
    // Normalised tool invocations map the Copilot hook surfaces:
    //   tool = 'copilot:completion-requested' -> completionRequested gate
    //   tool = 'copilot:completion-received'  -> completionReceived ingress
    //   tool = 'copilot:chat-requested'       -> chatRequested gate
    //   tool = 'copilot:chat-received'        -> chatReceived ingress
    //   tool = 'copilot:suggestion-accepted'  -> audit event
    //   tool = 'copilot:suggestion-rejected'  -> audit event (optional)
    // ---------------------------------------------------------------------------
    async interceptToolInvocation(ctx) {
        this.ensureInitialized();
        switch (ctx.tool) {
            case 'copilot:completion-requested':
                return this._gateCompletionRequest(ctx);
            case 'copilot:completion-received':
                return this._scanCompletionResponse(ctx);
            case 'copilot:chat-requested':
                return this._gateChatRequest(ctx);
            case 'copilot:chat-received':
                return this._scanChatResponse(ctx);
            case 'copilot:suggestion-accepted':
                return this._auditSuggestionAccepted(ctx);
            case 'copilot:suggestion-rejected':
                return this._auditSuggestionRejected(ctx);
            default:
                // Unknown Copilot surface -- allow + log. Editor shim may send
                // other normalised events; do not block unknown surfaces.
                this.emit(buildIdeAuditEvent('ide:tool-invocation-approved', {
                    ideId: this.ideId,
                    adapterId: this.adapterId,
                    sessionId: ctx.sessionHandle.sessionId,
                    initiatingUserId: ctx.initiatingUser.userId,
                    tool: ctx.tool,
                    detail: `GitHubCopilotIdeAdapter: unknown surface '${ctx.tool}' -- allowed with audit.`,
                }));
                return this.routeToolInvocation(ctx, {
                    decision: 'ALLOW',
                    reason: `Unknown Copilot surface '${ctx.tool}' permitted with audit.`,
                });
        }
    }
    // ---------------------------------------------------------------------------
    // Hook surface: completionRequested -- pre-completion gate (G2 PT-ADAPT-GHCOPILOT-1)
    // ---------------------------------------------------------------------------
    async _gateCompletionRequest(ctx) {
        const filePath = String(ctx.input['filePath'] ?? ctx.input['file_path'] ?? '');
        const surroundingContext = String(ctx.input['surroundingContext'] ?? ctx.input['context'] ?? '');
        const language = String(ctx.input['language'] ?? '');
        // 1. Content Exclusion path check
        const exclusionViolation = this._checkContentExclusion(filePath);
        if (exclusionViolation) {
            const decision = {
                decision: 'DENY',
                reason: `File '${filePath}' matches a Content Exclusion path configured ` +
                    `in the pack binding (${exclusionViolation}). Completion request blocked.`,
                metadata: { filePath, exclusionPattern: exclusionViolation },
            };
            return this.routeToolInvocation(ctx, decision);
        }
        // 2. Secret pattern scan on surrounding context
        const secretMatch = this._scanTextForSecrets(surroundingContext);
        if (secretMatch) {
            const decision = {
                decision: 'DENY',
                reason: `Completion request blocked by GitHubCopilotIdeAdapter: ` +
                    `secret pattern '${secretMatch}' detected in surrounding code context. ` +
                    `Remove the secret from context before requesting a completion.`,
                metadata: { filePath, secretPattern: secretMatch, language },
            };
            return this.routeToolInvocation(ctx, decision);
        }
        // 3. PHI proximity indicator check under HIPAA pack
        if (this._isHipaaBinding()) {
            const phiIndicator = this._scanTextForPhiIndicators(surroundingContext);
            if (phiIndicator) {
                const tier = this.copilotConfig?.hipaaReviewerTier ?? 'T2_LEAD';
                const decision = {
                    decision: 'REQUIRE_APPROVAL',
                    reason: `Completion request routed for review under HIPAA pack binding: ` +
                        `PHI proximity indicator '${phiIndicator}' found in surrounding context. ` +
                        `Reviewer tier: ${tier}.`,
                    metadata: { filePath, phiIndicator, language, tier },
                };
                return this.routeToolInvocation(ctx, decision);
            }
        }
        // 4. Non-BAA-eligible flavour under HIPAA pack
        const flavourDecision = this._checkFlavourCompliance(ctx);
        if (flavourDecision)
            return flavourDecision;
        const decision = {
            decision: 'ALLOW',
            reason: `Completion request for '${filePath}' permitted by GitHubCopilotIdeAdapter.`,
            metadata: { filePath, language },
        };
        return this.routeToolInvocation(ctx, decision);
    }
    // ---------------------------------------------------------------------------
    // Hook surface: completionReceived -- post-completion ingress (G2 PT-ADAPT-GHCOPILOT-2)
    // ---------------------------------------------------------------------------
    async _scanCompletionResponse(ctx) {
        const completionText = String(ctx.input['completionText'] ?? ctx.input['completion'] ?? '');
        const completionId = String(ctx.input['completionId'] ?? '');
        // Scan returned completion for secret material
        const secretMatch = this._scanTextForSecrets(completionText);
        if (secretMatch) {
            const decision = {
                decision: 'DENY',
                reason: `Completion response blocked by GitHubCopilotIdeAdapter: ` +
                    `secret pattern '${secretMatch}' found in completion text. ` +
                    `GitHub Copilot has historically surfaced API keys from training data. ` +
                    `This completion will not be surfaced to the developer.`,
                metadata: { completionId, secretPattern: secretMatch },
            };
            return this.routeToolInvocation(ctx, decision);
        }
        this.emit(buildIdeAuditEvent('ide:tool-invocation-approved', {
            ideId: this.ideId,
            adapterId: this.adapterId,
            sessionId: ctx.sessionHandle.sessionId,
            initiatingUserId: ctx.initiatingUser.userId,
            tool: ctx.tool,
            detail: `Completion response passed secret-scan ingress (completionId: ${completionId || 'n/a'}).`,
            data: { completionId },
        }));
        return this.routeToolInvocation(ctx, {
            decision: 'ALLOW',
            reason: 'Completion response passed secret-scan ingress.',
            metadata: { completionId },
        });
    }
    // ---------------------------------------------------------------------------
    // Hook surface: chatRequested -- pre-chat gate
    // ---------------------------------------------------------------------------
    async _gateChatRequest(ctx) {
        const promptText = String(ctx.input['promptText'] ?? ctx.input['prompt'] ?? '');
        const attachedFilePaths = ctx.input['attachedFilePaths'] ?? [];
        // 1. Secret scan on prompt body
        const secretMatch = this._scanTextForSecrets(promptText);
        if (secretMatch) {
            const decision = {
                decision: 'DENY',
                reason: `Chat request blocked by GitHubCopilotIdeAdapter: ` +
                    `secret pattern '${secretMatch}' detected in prompt text.`,
                metadata: { secretPattern: secretMatch },
            };
            return this.routeToolInvocation(ctx, decision);
        }
        // 2. Content Exclusion check on attached files
        for (const fp of attachedFilePaths) {
            const exclusionViolation = this._checkContentExclusion(fp);
            if (exclusionViolation) {
                const decision = {
                    decision: 'DENY',
                    reason: `Chat request blocked: attached file '${fp}' matches Content ` +
                        `Exclusion path '${exclusionViolation}'. Remove the file from context.`,
                    metadata: { filePath: fp, exclusionPattern: exclusionViolation },
                };
                return this.routeToolInvocation(ctx, decision);
            }
        }
        // 3. PHI indicator check under HIPAA pack
        if (this._isHipaaBinding()) {
            const phiIndicator = this._scanTextForPhiIndicators(promptText);
            if (phiIndicator) {
                const tier = this.copilotConfig?.hipaaReviewerTier ?? 'T2_LEAD';
                const decision = {
                    decision: 'REQUIRE_APPROVAL',
                    reason: `Chat request routed for HIPAA review: PHI indicator '${phiIndicator}' ` +
                        `in prompt. Reviewer tier: ${tier}.`,
                    metadata: { phiIndicator, tier },
                };
                return this.routeToolInvocation(ctx, decision);
            }
        }
        // 4. Flavour compliance check
        const flavourDecision = this._checkFlavourCompliance(ctx);
        if (flavourDecision)
            return flavourDecision;
        return this.routeToolInvocation(ctx, {
            decision: 'ALLOW',
            reason: 'Chat request passed GitHubCopilotIdeAdapter gates.',
        });
    }
    // ---------------------------------------------------------------------------
    // Hook surface: chatReceived -- post-chat ingress classification
    // ---------------------------------------------------------------------------
    async _scanChatResponse(ctx) {
        const responseText = String(ctx.input['responseText'] ?? ctx.input['response'] ?? '');
        const threadId = String(ctx.input['threadId'] ?? '');
        const secretMatch = this._scanTextForSecrets(responseText);
        if (secretMatch) {
            const decision = {
                decision: 'DENY',
                reason: `Chat response blocked by GitHubCopilotIdeAdapter: ` +
                    `secret pattern '${secretMatch}' found in response text.`,
                metadata: { threadId, secretPattern: secretMatch },
            };
            return this.routeToolInvocation(ctx, decision);
        }
        return this.routeToolInvocation(ctx, {
            decision: 'ALLOW',
            reason: 'Chat response passed secret-scan ingress.',
            metadata: { threadId },
        });
    }
    // ---------------------------------------------------------------------------
    // Hook surface: suggestionAccepted -- audit event
    // ---------------------------------------------------------------------------
    async _auditSuggestionAccepted(ctx) {
        const targetFilePath = String(ctx.input['targetFilePath'] ?? ctx.input['file_path'] ?? '');
        const completionId = String(ctx.input['completionId'] ?? '');
        const suggestionText = String(ctx.input['suggestionText'] ?? ctx.input['suggestion'] ?? '');
        this.emit(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 Copilot suggestion. ` +
                `Target: ${targetFilePath || 'unknown'}. ` +
                `OriginalCompletionId: ${completionId || 'n/a'}. ` +
                `SuggestionLength: ${suggestionText.length} chars.`,
            data: { targetFilePath, completionId, suggestionLength: suggestionText.length },
        }));
        return this.routeToolInvocation(ctx, {
            decision: 'ALLOW',
            reason: `Suggestion acceptance audited for target '${targetFilePath}'.`,
            metadata: { targetFilePath, completionId },
        });
    }
    // ---------------------------------------------------------------------------
    // Hook surface: suggestionRejected -- optional audit event
    // ---------------------------------------------------------------------------
    async _auditSuggestionRejected(ctx) {
        const reason = String(ctx.input['reason'] ?? 'not-specified');
        const completionId = String(ctx.input['completionId'] ?? '');
        this.emit(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 Copilot suggestion. Reason: ${reason}. CompletionId: ${completionId || 'n/a'}.`,
            data: { reason, completionId },
        }));
        return this.routeToolInvocation(ctx, {
            decision: 'ALLOW',
            reason: `Suggestion rejection audited. Reason: ${reason}.`,
            metadata: { reason, completionId },
        });
    }
    // ---------------------------------------------------------------------------
    // Capabilities
    // ---------------------------------------------------------------------------
    getIdeCapabilities() {
        return {
            runtimeHooks: true,
            prBoundary: false,
            userIdentity: true,
            reviewerRouting: true,
            integrationMode: 'copilot-normalised-event-shim',
        };
    }
    // ---------------------------------------------------------------------------
    // Error translation
    // ---------------------------------------------------------------------------
    translateError(raw) {
        if (raw instanceof IdeAttributionError)
            return raw;
        if (raw instanceof IdeUnsupportedError)
            return raw;
        return super.translateError(raw);
    }
    // ---------------------------------------------------------------------------
    // Helpers
    // ---------------------------------------------------------------------------
    /**
     * Scan text for known secret patterns. Returns the pattern name on first
     * match, or null if no match. One match is enough to act.
     */
    _scanTextForSecrets(text) {
        if (!text)
            return null;
        for (const { name, re } of COPILOT_COMPLETION_SECRET_PATTERNS) {
            if (re.test(text))
                return name;
        }
        return null;
    }
    /**
     * Scan text for PHI proximity indicators. Returns the pattern source on
     * first match, or null.
     */
    _scanTextForPhiIndicators(text) {
        if (!text)
            return null;
        for (const re of PHI_CONTEXT_INDICATORS) {
            if (re.test(text))
                return re.source;
        }
        return null;
    }
    /**
     * Check whether the given file path matches any Content Exclusion path
     * configured in the pack binding. Returns the matching exclusion path, or
     * null.
     *
     * NOTE: This supplements the GitHub Content Exclusion setting -- it does
     * not replace it. The customer must also configure Content Exclusion in
     * their GitHub org settings for it to take effect at the GitHub network level.
     */
    _checkContentExclusion(filePath) {
        if (!filePath)
            return null;
        const exclusionPaths = this.copilotConfig?.contentExclusionPaths ?? [];
        for (const pattern of exclusionPaths) {
            if (filePath.startsWith(pattern) || filePath.includes(pattern)) {
                return pattern;
            }
        }
        return null;
    }
    /**
     * True if the pack binding includes HIPAA.
     */
    _isHipaaBinding() {
        const packId = this.config?.packId ?? '';
        return packId === 'hipaa' || packId.startsWith('hipaa');
    }
    /**
     * Check that the Copilot flavour is BAA-eligible when a HIPAA binding is
     * active. Returns a DENY decision if the flavour is non-BAA-eligible,
     * or null if no issue is found.
     *
     * PT-ADAPT-GHCOPILOT-3: Copilot Enterprise tenant with non-BAA
     * data-retention setting is blocked under HIPAA binding.
     */
    _checkFlavourCompliance(ctx) {
        if (!this._isHipaaBinding())
            return null;
        const flavour = this.copilotConfig?.copilotFlavour ?? 'business';
        if (!COPILOT_BAA_ELIGIBLE_FLAVOURS.includes(flavour)) {
            const decision = {
                decision: 'DENY',
                reason: `GitHubCopilotIdeAdapter: Copilot flavour '${flavour}' is not BAA-eligible ` +
                    `under HIPAA binding '${this.config?.packId}'. ` +
                    `Upgrade to Copilot Business or Copilot Enterprise to use Copilot ` +
                    `in a HIPAA-regulated context. ` +
                    `Retention posture: ${COPILOT_DATA_RETENTION[flavour]}.`,
                metadata: {
                    copilotFlavour: flavour,
                    hipaaPackId: this.config?.packId,
                    retentionPosture: COPILOT_DATA_RETENTION[flavour],
                },
            };
            return decision;
        }
        return null;
    }
    /** Expose the resolved Copilot flavour for diagnostics / tests. */
    getCopilotFlavour() {
        return this.copilotConfig?.copilotFlavour ?? 'business';
    }
    /** Expose the public-code filter setting for diagnostics / tests. */
    getPublicCodeFilter() {
        return this.copilotConfig?.publicCodeFilter ?? 'unknown';
    }
}
//# sourceMappingURL=github-copilot.js.map