"use strict";
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2024-2026 Gracker (Chris)
// This file is part of SmartPerfetto. See LICENSE for details.
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.__testing = exports.WorktreeError = void 0;
exports.createWorktree = createWorktree;
exports.removeWorktree = removeWorktree;
exports.withWorktree = withWorktree;
/**
 * Git worktree wrapper for strategy auto-patch.
 *
 * Auto-patch must NEVER touch the developer's primary working tree: a failed
 * test, a regression, or a crash mid-patch could leave dirty state on `main`
 * and confuse the human reviewing the patch. Every patch attempt runs in a
 * tmp worktree keyed by jobId so multiple jobs can run in parallel without
 * stepping on each other.
 *
 * `withWorktree(opts, fn)` is the recommended entry — it guarantees cleanup
 * via try/finally even if `fn` throws.
 *
 * See docs/architecture/self-improving-design.md §13.2 (Worktree isolation).
 */
const child_process_1 = require("child_process");
const util_1 = require("util");
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const execFile = (0, util_1.promisify)(child_process_1.execFile);
/** Whitelist for jobId — prevents shell injection and path traversal in tmp dir name. */
const JOB_ID_RE = /^[a-zA-Z0-9_-]{1,64}$/;
/** In-flight jobIds; rejecting a duplicate avoids racing on the same tmp path. */
const ACTIVE_JOBS = new Set();
class WorktreeError extends Error {
    // Manual `cause` because tsconfig lib targets ES2020; ES2022's Error.cause
    // option to `super()` isn't available without a project-wide lib bump.
    constructor(message, cause) {
        super(message);
        this.cause = cause;
        this.name = 'WorktreeError';
    }
}
exports.WorktreeError = WorktreeError;
function assertValidJobId(jobId) {
    if (!JOB_ID_RE.test(jobId)) {
        throw new WorktreeError(`invalid jobId "${jobId}" — must match ${JOB_ID_RE.source}`);
    }
}
function resolveWorktreePath(jobId) {
    return path.join(os.tmpdir(), `sp-autopatch-${jobId}`);
}
/**
 * Create a git worktree at `<tmpdir>/sp-autopatch-<jobId>` based on `baseRef`.
 *
 * Throws WorktreeError if jobId is invalid, already in flight, or git refuses.
 * Callers should pair this with `removeWorktree` in a `finally` block, or
 * simply use `withWorktree` which handles that automatically.
 */
async function createWorktree(opts) {
    assertValidJobId(opts.jobId);
    if (ACTIVE_JOBS.has(opts.jobId)) {
        throw new WorktreeError(`jobId "${opts.jobId}" already has an active worktree`);
    }
    const baseRef = opts.baseRef || 'main';
    const workingDir = opts.workingDir || process.cwd();
    const worktreePath = resolveWorktreePath(opts.jobId);
    ACTIVE_JOBS.add(opts.jobId);
    try {
        await execFile('git', ['worktree', 'add', '--detach', worktreePath, baseRef], {
            cwd: workingDir,
        });
    }
    catch (err) {
        ACTIVE_JOBS.delete(opts.jobId);
        throw new WorktreeError(`failed to create worktree at ${worktreePath} from ${baseRef}: ${err.message}`, err);
    }
    return { jobId: opts.jobId, worktreePath, baseRef, workingDir };
}
/**
 * Remove a previously-created worktree. Idempotent: if the path doesn't exist
 * git emits a recoverable error, which we swallow when `force === true` so
 * cleanup never masks the original error from the caller's main flow. The
 * swallowed error is logged for debuggability.
 */
async function removeWorktree(handle, force = true) {
    const args = ['worktree', 'remove'];
    if (force)
        args.push('--force');
    args.push(handle.worktreePath);
    try {
        await execFile('git', args, { cwd: handle.workingDir });
    }
    catch (err) {
        if (force) {
            console.warn(`[worktreeRunner] best-effort cleanup of ${handle.worktreePath} failed: ${err.message}`);
            return;
        }
        throw new WorktreeError(`failed to remove worktree ${handle.worktreePath}: ${err.message}`, err);
    }
    finally {
        ACTIVE_JOBS.delete(handle.jobId);
    }
}
/**
 * Run `fn` inside a freshly-created worktree, cleaning up afterwards.
 *
 * Cleanup uses `force=true` so a failure inside `fn` doesn't leave an orphan
 * worktree on disk; the original error from `fn` always propagates.
 */
async function withWorktree(opts, fn) {
    const handle = await createWorktree(opts);
    try {
        return await fn(handle);
    }
    finally {
        await removeWorktree(handle, true);
    }
}
/** Exposed for tests so they can inspect path resolution and validation rules. */
exports.__testing = { resolveWorktreePath, JOB_ID_RE, ACTIVE_JOBS };
//# sourceMappingURL=worktreeRunner.js.map