/**
 * ContinueDevIdeAdapter -- Continue.dev IDE adapter.
 *
 * Implements the IdeAdapter contract for Continue.dev, an open-source
 * AI code assistant (https://continue.dev). Enforcement fires via the
 * Continue.dev core SDK extension-point surface:
 *
 *   IExtensionPoint.onToolCallStart   -- PreToolUse equivalent
 *   IExtensionPoint.onToolCallEnd     -- PostToolUse equivalent
 *   IExtensionPoint.onChatRequest     -- chat-level interception
 *
 * The adapter exposes a factory method `buildContinueExtension()` that
 * produces a Continue.dev extension object the customer adds to their
 * config.ts `extensions` array. The extension hooks wire governance
 * enforcement into every tool call.
 *
 * TESTED_API_VERSION is pinned to '0.8'. Continue.dev is pre-1.0 and
 * the extension API may shift. If the upstream version reported at
 * dispatch time is below this minimum, the adapter emits
 * `ide:api-version-mismatch` WARN on first dispatch.
 *
 * DEFERRED-CONTINUE-API-VERSION: Once Continue.dev publishes a stable
 * SDK version (1.0+), update TESTED_API_VERSION and remove this flag.
 * Add a KANBAN task to track.
 *
 * User identity: resolved from the Continue config's `userId` field.
 * If missing and allowAnonymousIdeSessions is not explicitly true,
 * initialize() throws IdeAttributionError (fail-closed per AC 22).
 *
 * Privileged actions (filesystem writes outside project root, shell
 * commands, git mutations) are routed to the configured reviewer tier.
 *
 * Destructive-command denial and sensitive-path protection reuse the
 * shared patterns from src/ide-adapters/shared.ts.
 *
 * Spec: P3-IDE-01 AC 13-14, Section 2.5
 *
 * @connexum/ai-governance
 */
import { BaseIdeAdapter } from './base.js';
import { IdeAttributionError, } from './index.js';
import { buildIdeAuditEvent, isDestructiveCommand, isSensitivePath } from './shared.js';
// ---------------------------------------------------------------------------
// Continue.dev API version pinning (AC 14, AC 20)
// ---------------------------------------------------------------------------
/**
 * Minimum Continue.dev SDK version this adapter is tested against.
 * Emit ide:api-version-mismatch WARN at first dispatch if the upstream
 * version falls below this.
 *
 * DEFERRED-CONTINUE-API-VERSION: Verify and update once Continue.dev
 * publishes a stable SDK (1.0+).
 */
export const CONTINUE_TESTED_API_VERSION = '0.8';
// ---------------------------------------------------------------------------
// Privileged action matchers
// ---------------------------------------------------------------------------
/** Shell/terminal tool names reported by Continue.dev. */
const SHELL_TOOL_NAMES = new Set([
    'run_terminal',
    'run_command',
    'execute',
    'bash',
    'shell',
]);
/** Git mutation tool names. */
const GIT_MUTATION_TOOLS = new Set([
    'git_commit',
    'git_push',
    'git_checkout',
    'git_reset',
    'git_rebase',
]);
/** File-write tool names. */
const FILE_WRITE_TOOLS = new Set([
    'write_file',
    'edit_file',
    'create_file',
    'apply_edit',
    'Edit',
    'Write',
]);
// ---------------------------------------------------------------------------
// ContinueDevIdeAdapter
// ---------------------------------------------------------------------------
export class ContinueDevIdeAdapter extends BaseIdeAdapter {
    adapterId = 'continue-dev';
    ideId = 'continue-dev';
    continueConfig = null;
    versionWarnEmitted = false;
    // ---------------------------------------------------------------------------
    // Lifecycle
    // ---------------------------------------------------------------------------
    initialize(config) {
        // Base performs pack-binding guard
        super.initialize(config);
        const continueConf = config;
        this.continueConfig = continueConf;
        // Resolve user identity eagerly at initialize time (fail-closed per AC 22).
        // The userId is expected in options.userId (representing the Continue config
        // `userId` field). If missing and anonymous sessions are not permitted,
        // throw IdeAttributionError now.
        const userId = this._extractUserId(config);
        const allowAnon = config.allowAnonymousIdeSessions === true;
        if (!userId && !allowAnon) {
            throw new IdeAttributionError({
                message: 'ContinueDevIdeAdapter: cannot resolve user identity. The Continue.dev ' +
                    'config must include a `userId` field, passed as options.userId in ' +
                    'IdeAdapterConfig. To permit anonymous sessions, set ' +
                    'allowAnonymousIdeSessions: true in adapter config.',
                ideId: this.ideId,
                adapterId: this.adapterId,
            });
        }
    }
    // ---------------------------------------------------------------------------
    // IdeAdapter: resolveUserIdentity
    // ---------------------------------------------------------------------------
    async resolveUserIdentity(session) {
        this.ensureInitialized();
        // Priority: session context identity > config userId > anonymous
        const fromSession = session.identity?.userId?.trim();
        if (fromSession) {
            const identity = {
                userId: fromSession,
                attributionMethod: 'continue-config',
            };
            if (session.identity?.email)
                identity.email = session.identity.email;
            if (session.identity?.orgId)
                identity.orgId = session.identity.orgId;
            if (session.identity?.githubLogin)
                identity.githubLogin = session.identity.githubLogin;
            this.emitIdentityResolved(identity.userId, session.clientSessionId);
            return identity;
        }
        const configUserId = this._extractUserId(this.config);
        if (configUserId) {
            const identity = { userId: configUserId, attributionMethod: 'continue-config' };
            this.emitIdentityResolved(identity.userId, session.clientSessionId);
            return identity;
        }
        const allowAnon = this.config?.allowAnonymousIdeSessions === true;
        if (allowAnon) {
            const anon = { userId: 'anonymous', attributionMethod: 'continue-config' };
            this.emitIdentityResolved(anon.userId, session.clientSessionId);
            return anon;
        }
        this.emitIdentityUnresolved(session.clientSessionId, 'ContinueDevIdeAdapter: no userId found in session context or Continue config.');
        throw new IdeAttributionError({
            message: 'ContinueDevIdeAdapter: cannot resolve user identity. Provide userId ' +
                'in the session context or in adapter config options.userId.',
            ideId: this.ideId,
            adapterId: this.adapterId,
        });
    }
    // ---------------------------------------------------------------------------
    // IdeAdapter: interceptToolInvocation
    // ---------------------------------------------------------------------------
    async interceptToolInvocation(ctx) {
        this.ensureInitialized();
        // 1. API version mismatch check (AC 14, AC 20)
        const reportedVersion = ctx.ideMetadata?.['sdkVersion'];
        if (reportedVersion && !this.versionWarnEmitted) {
            if (!this._meetsMinVersion(reportedVersion, CONTINUE_TESTED_API_VERSION)) {
                this.versionWarnEmitted = true;
                this.emit(buildIdeAuditEvent('ide:api-version-mismatch', {
                    ideId: this.ideId,
                    adapterId: this.adapterId,
                    sessionId: ctx.sessionHandle.sessionId,
                    detail: `Continue.dev SDK version '${reportedVersion}' is below the ` +
                        `tested minimum '${CONTINUE_TESTED_API_VERSION}'. Governance ` +
                        `enforcement may not behave as expected. Update Continue.dev or ` +
                        `set continueApiVersion in adapter config to suppress this warning.`,
                    data: { reportedVersion, testedMinVersion: CONTINUE_TESTED_API_VERSION },
                }));
            }
        }
        // 2. Destructive command denial (shell tool names)
        if (SHELL_TOOL_NAMES.has(ctx.tool.toLowerCase())) {
            const command = String(ctx.input['command'] ?? ctx.input['cmd'] ?? '');
            if (command) {
                const result = isDestructiveCommand(command);
                if (result.matched) {
                    const decision = {
                        decision: 'DENY',
                        reason: `Destructive command blocked by ContinueDevIdeAdapter. ` +
                            `Pattern: ${result.pattern?.source ?? 'unknown'}`,
                        metadata: { pattern: result.pattern?.source },
                    };
                    return this.routeToolInvocation(ctx, decision);
                }
            }
        }
        // 3. Sensitive path protection (file tools)
        if (FILE_WRITE_TOOLS.has(ctx.tool) || FILE_WRITE_TOOLS.has(ctx.tool.toLowerCase())) {
            const filePath = String(ctx.input['file_path'] ?? ctx.input['filePath'] ?? ctx.input['path'] ?? '');
            if (filePath) {
                const result = isSensitivePath(filePath);
                if (result.matched) {
                    const decision = {
                        decision: 'DENY',
                        reason: `Sensitive path blocked by ContinueDevIdeAdapter: ${filePath}`,
                        metadata: { filePath, pattern: result.pattern?.source },
                    };
                    return this.routeToolInvocation(ctx, decision);
                }
            }
        }
        // 4. Privileged action routing: writes outside project root, shell, git mutations
        const tier = this.continueConfig?.privilegedActionReviewerTier ?? 'T2_LEAD';
        if (this._isPrivilegedAction(ctx)) {
            // Route to reviewer but do not auto-deny; reviewer decides
            const decision = {
                decision: 'REQUIRE_APPROVAL',
                reason: `Privileged action '${ctx.tool}' requires ${tier} review (ContinueDevIdeAdapter).`,
                metadata: { tier },
            };
            return this.routeToolInvocation(ctx, decision);
        }
        // 5. Default: allow
        const decision = {
            decision: 'ALLOW',
            reason: `Tool '${ctx.tool}' permitted by ContinueDevIdeAdapter.`,
        };
        return this.routeToolInvocation(ctx, decision);
    }
    // ---------------------------------------------------------------------------
    // Continue.dev extension factory
    //
    // Returns a ContinueGovernanceExtension object the customer places in their
    // Continue.dev config.ts `extensions` array.
    // ---------------------------------------------------------------------------
    buildContinueExtension(sessionHandle, identity) {
        const adapter = this;
        return {
            name: '@connexum/ai-governance',
            async onToolCallStart(event) {
                const ctx = {
                    tool: event.tool,
                    input: event.input,
                    sessionHandle,
                    initiatingUser: identity,
                    timestamp: new Date().toISOString(),
                    ideMetadata: {
                        sdkVersion: event.sdkVersion,
                        sessionId: event.sessionId,
                    },
                };
                try {
                    const decision = await adapter.interceptToolInvocation(ctx);
                    return {
                        allow: decision.decision === 'ALLOW' || decision.decision === 'REQUIRE_APPROVAL',
                        reason: decision.reason,
                    };
                }
                catch (err) {
                    const translated = adapter.translateError(err);
                    return { allow: false, reason: translated.message };
                }
            },
            async onToolCallEnd(event, result) {
                const ctx = {
                    tool: event.tool,
                    input: event.input,
                    sessionHandle,
                    initiatingUser: identity,
                    timestamp: new Date().toISOString(),
                    ideMetadata: { sdkVersion: event.sdkVersion },
                };
                const toolResult = {
                    success: true,
                    outputSummary: typeof result === 'string' ? result.slice(0, 200) : '[result]',
                };
                await adapter.recordToolResult(ctx, toolResult);
            },
            async onChatRequest(event) {
                // Chat requests are not governed at the tool level; emit session event only.
                adapter.emit(buildIdeAuditEvent('ide:session-open', {
                    ideId: adapter.ideId,
                    adapterId: adapter.adapterId,
                    sessionId: sessionHandle.sessionId,
                    initiatingUserId: event.userId ?? identity.userId,
                    detail: `Continue.dev chat request received`,
                    data: { sessionId: event.sessionId, sdkVersion: event.sdkVersion },
                }));
            },
        };
    }
    // ---------------------------------------------------------------------------
    // Capabilities
    // ---------------------------------------------------------------------------
    getIdeCapabilities() {
        return {
            runtimeHooks: true,
            prBoundary: false,
            userIdentity: true,
            reviewerRouting: true,
            integrationMode: 'continue-sdk-extension',
        };
    }
    // ---------------------------------------------------------------------------
    // Helpers
    // ---------------------------------------------------------------------------
    _extractUserId(config) {
        const uid = config.options?.['userId'];
        if (typeof uid === 'string' && uid.trim())
            return uid.trim();
        return null;
    }
    /**
     * True if `reported` is at or above `minimum` (semver major.minor comparison).
     * Handles versions like '0.8', '0.9.1', '1.0.0'.
     */
    _meetsMinVersion(reported, minimum) {
        const parse = (v) => v.split('.').map((s) => parseInt(s, 10) || 0);
        const r = parse(reported);
        const m = parse(minimum);
        const len = Math.max(r.length, m.length);
        for (let i = 0; i < len; i++) {
            const rv = r[i] ?? 0;
            const mv = m[i] ?? 0;
            if (rv > mv)
                return true;
            if (rv < mv)
                return false;
        }
        return true; // equal
    }
    /**
     * Returns true if the tool invocation is considered privileged and should
     * be routed for reviewer approval before execution.
     */
    _isPrivilegedAction(ctx) {
        const tool = ctx.tool.toLowerCase();
        // Shell commands are always privileged
        if (SHELL_TOOL_NAMES.has(tool))
            return true;
        // Git mutations are always privileged
        if (GIT_MUTATION_TOOLS.has(tool) || GIT_MUTATION_TOOLS.has(ctx.tool))
            return true;
        // File writes outside the project root
        if (FILE_WRITE_TOOLS.has(ctx.tool) || FILE_WRITE_TOOLS.has(tool)) {
            const projectRoot = this.continueConfig?.projectRoot;
            if (projectRoot) {
                const filePath = String(ctx.input['file_path'] ?? ctx.input['filePath'] ?? ctx.input['path'] ?? '');
                if (filePath && !filePath.startsWith(projectRoot)) {
                    return true;
                }
            }
        }
        return false;
    }
}
//# sourceMappingURL=continue-dev.js.map