
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="726b8dc0-c40b-5fd0-bd16-d1fc5a223193")}catch(e){}}();
import { spawnSync } from "node:child_process";
import { randomBytes } from "node:crypto";
import { readFile, realpath, rm } from "node:fs/promises";
import { homedir } from "node:os";
import { basename, join, resolve } from "node:path";
import { z } from "zod";
import { runtimeBoardDataSchema, runtimeTaskSessionSummarySchema, runtimeWorkspaceStateSaveRequestSchema, } from "../core/api-contract.js";
import { createGitProcessEnv } from "../core/git-process-env.js";
import { updateTaskDependencies } from "../core/task-board-mutations.js";
import { lockedFileSystem } from "../fs/locked-file-system.js";
const RUNTIME_HOME_PARENT_DIR = ".cline";
const RUNTIME_HOME_DIR = "kanban";
const RUNTIME_WORKTREES_DIR = "worktrees";
const WORKSPACES_DIR = "workspaces";
const INDEX_FILENAME = "index.json";
const BOARD_FILENAME = "board.json";
const SESSIONS_FILENAME = "sessions.json";
const META_FILENAME = "meta.json";
const INDEX_VERSION = 1;
const WORKSPACE_ID_COLLISION_SUFFIX_LENGTH = 4;
const BOARD_COLUMNS = [
    { id: "backlog", title: "Backlog" },
    { id: "in_progress", title: "In Progress" },
    { id: "review", title: "Review" },
    { id: "trash", title: "Trash" },
];
const workspaceStateMetaSchema = z.object({
    revision: z.number().int().nonnegative(),
    updatedAt: z.number(),
});
const workspaceIndexEntrySchema = z.object({
    workspaceId: z.string().min(1, "Workspace ID cannot be empty."),
    repoPath: z.string().min(1, "Workspace repository path cannot be empty."),
});
const workspaceIndexFileSchema = z
    .object({
    version: z.literal(INDEX_VERSION),
    entries: z.record(z.string(), workspaceIndexEntrySchema),
    repoPathToId: z.record(z.string(), z.string().min(1, "Workspace ID cannot be empty.")),
})
    .superRefine((index, context) => {
    for (const [workspaceId, entry] of Object.entries(index.entries)) {
        if (entry.workspaceId !== workspaceId) {
            context.addIssue({
                code: z.ZodIssueCode.custom,
                path: ["entries", workspaceId, "workspaceId"],
                message: `Workspace ID must match entry key "${workspaceId}".`,
            });
        }
        const mappedWorkspaceId = index.repoPathToId[entry.repoPath];
        if (mappedWorkspaceId !== workspaceId) {
            context.addIssue({
                code: z.ZodIssueCode.custom,
                path: ["entries", workspaceId, "repoPath"],
                message: `Missing repoPathToId mapping for "${entry.repoPath}" to "${workspaceId}".`,
            });
        }
    }
    for (const [repoPath, workspaceId] of Object.entries(index.repoPathToId)) {
        const entry = index.entries[workspaceId];
        if (!entry) {
            context.addIssue({
                code: z.ZodIssueCode.custom,
                path: ["repoPathToId", repoPath],
                message: `Mapped workspace "${workspaceId}" does not exist in entries.`,
            });
            continue;
        }
        if (entry.repoPath !== repoPath) {
            context.addIssue({
                code: z.ZodIssueCode.custom,
                path: ["repoPathToId", repoPath],
                message: `Mapped repoPath does not match workspace entry path "${entry.repoPath}".`,
            });
        }
    }
});
const workspaceSessionsSchema = z
    .record(z.string(), runtimeTaskSessionSummarySchema)
    .superRefine((sessions, context) => {
    for (const [taskId, session] of Object.entries(sessions)) {
        if (session.taskId !== taskId) {
            context.addIssue({
                code: z.ZodIssueCode.custom,
                path: [taskId, "taskId"],
                message: `Session taskId must match record key "${taskId}".`,
            });
        }
    }
});
function createEmptyBoard() {
    return {
        columns: BOARD_COLUMNS.map((column) => ({
            id: column.id,
            title: column.title,
            cards: [],
        })),
        dependencies: [],
    };
}
function createEmptyWorkspaceIndex() {
    return {
        version: INDEX_VERSION,
        entries: {},
        repoPathToId: {},
    };
}
export function getRuntimeHomePath() {
    return join(homedir(), RUNTIME_HOME_PARENT_DIR, RUNTIME_HOME_DIR);
}
export function getTaskWorktreesHomePath() {
    return join(homedir(), RUNTIME_HOME_PARENT_DIR, RUNTIME_WORKTREES_DIR);
}
export function getWorkspacesRootPath() {
    return join(getRuntimeHomePath(), WORKSPACES_DIR);
}
function getWorkspaceIndexPath() {
    return join(getWorkspacesRootPath(), INDEX_FILENAME);
}
export function getWorkspaceDirectoryPath(workspaceId) {
    return join(getWorkspacesRootPath(), workspaceId);
}
function getWorkspaceBoardPath(workspaceId) {
    return join(getWorkspaceDirectoryPath(workspaceId), BOARD_FILENAME);
}
function getWorkspaceSessionsPath(workspaceId) {
    return join(getWorkspaceDirectoryPath(workspaceId), SESSIONS_FILENAME);
}
function getWorkspaceMetaPath(workspaceId) {
    return join(getWorkspaceDirectoryPath(workspaceId), META_FILENAME);
}
function getWorkspaceIndexLockRequest() {
    return {
        path: getWorkspaceIndexPath(),
        type: "file",
    };
}
function getWorkspaceDirectoryLockRequest(workspaceId) {
    return {
        path: getWorkspaceDirectoryPath(workspaceId),
        type: "directory",
        lockfilePath: join(getWorkspacesRootPath(), `${workspaceId}.lock`),
    };
}
function getWorkspacesRootLockRequest() {
    return {
        path: getWorkspacesRootPath(),
        type: "directory",
        lockfileName: ".workspaces.lock",
    };
}
function isNodeErrorWithCode(error, code) {
    return typeof error === "object" && error !== null && "code" in error && error.code === code;
}
async function readJsonFile(path) {
    try {
        const raw = await readFile(path, "utf8");
        try {
            return JSON.parse(raw);
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            throw new Error(`Malformed JSON in ${path}. ${message}`);
        }
    }
    catch (error) {
        if (isNodeErrorWithCode(error, "ENOENT")) {
            return null;
        }
        const message = error instanceof Error ? error.message : String(error);
        throw new Error(`Could not read JSON file at ${path}. ${message}`);
    }
}
function formatSchemaIssuePath(pathSegments) {
    if (pathSegments.length === 0) {
        return "root";
    }
    return pathSegments
        .map((segment) => {
        if (typeof segment === "number") {
            return `[${segment}]`;
        }
        return String(segment);
    })
        .join(".");
}
function formatSchemaIssues(error) {
    return error.issues.map((issue) => `${formatSchemaIssuePath(issue.path)}: ${issue.message}`).join("; ");
}
function parsePersistedStateFile(filePath, fileLabel, raw, schema, defaultValue) {
    if (raw === null) {
        return defaultValue;
    }
    const parsed = schema.safeParse(raw);
    if (!parsed.success) {
        throw new Error(`Invalid ${fileLabel} file at ${filePath}. ` +
            `Fix or remove the file. Validation errors: ${formatSchemaIssues(parsed.error)}`);
    }
    return parsed.data;
}
function parseWorkspaceIndex(rawIndex) {
    const indexPath = getWorkspaceIndexPath();
    return parsePersistedStateFile(indexPath, INDEX_FILENAME, rawIndex, workspaceIndexFileSchema, createEmptyWorkspaceIndex());
}
function parseWorkspaceStateSavePayload(payload) {
    const parsed = runtimeWorkspaceStateSaveRequestSchema.safeParse(payload);
    if (!parsed.success) {
        throw new Error(`Invalid workspace state save payload. ${formatSchemaIssues(parsed.error)}`);
    }
    return parsed.data;
}
async function readWorkspaceBoard(workspaceId) {
    const boardPath = getWorkspaceBoardPath(workspaceId);
    const rawBoard = await readJsonFile(boardPath);
    return updateTaskDependencies(parsePersistedStateFile(boardPath, BOARD_FILENAME, rawBoard, runtimeBoardDataSchema, createEmptyBoard()));
}
async function readWorkspaceSessions(workspaceId) {
    const sessionsPath = getWorkspaceSessionsPath(workspaceId);
    const rawSessions = await readJsonFile(sessionsPath);
    return parsePersistedStateFile(sessionsPath, SESSIONS_FILENAME, rawSessions, workspaceSessionsSchema, {});
}
async function readWorkspaceMeta(workspaceId) {
    const metaPath = getWorkspaceMetaPath(workspaceId);
    const rawMeta = await readJsonFile(metaPath);
    return parsePersistedStateFile(metaPath, META_FILENAME, rawMeta, workspaceStateMetaSchema, {
        revision: 0,
        updatedAt: 0,
    });
}
async function readWorkspaceIndex() {
    const raw = await readJsonFile(getWorkspaceIndexPath());
    return parseWorkspaceIndex(raw);
}
async function writeWorkspaceIndex(index) {
    await lockedFileSystem.writeJsonFileAtomic(getWorkspaceIndexPath(), index, {
        lock: null,
    });
}
function toWorkspaceIdBase(repoPath) {
    const trimmed = repoPath.trim().replace(/[\\/]+$/g, "");
    const folderName = basename(trimmed) || "project";
    const normalized = folderName
        .normalize("NFKD")
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, "-")
        .replace(/^-+|-+$/g, "");
    return normalized || "project";
}
function createWorkspaceIdCollisionSuffix(length) {
    const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
    let suffix = "";
    while (suffix.length < length) {
        const bytes = randomBytes(length);
        for (const byte of bytes) {
            suffix += alphabet[byte % alphabet.length] ?? "";
            if (suffix.length === length) {
                break;
            }
        }
    }
    return suffix;
}
function createWorkspaceId(index, repoPath) {
    const baseId = toWorkspaceIdBase(repoPath);
    if (!index.entries[baseId] || index.entries[baseId]?.repoPath === repoPath) {
        return baseId;
    }
    for (let attempt = 0; attempt < 256; attempt += 1) {
        const candidate = `${baseId}-${createWorkspaceIdCollisionSuffix(WORKSPACE_ID_COLLISION_SUFFIX_LENGTH)}`;
        if (!index.entries[candidate] || index.entries[candidate]?.repoPath === repoPath) {
            return candidate;
        }
    }
    throw new Error(`Could not generate a unique workspace ID for ${repoPath}.`);
}
function ensureWorkspaceEntry(index, repoPath) {
    const existingWorkspaceId = index.repoPathToId[repoPath];
    if (existingWorkspaceId) {
        const existingEntry = index.entries[existingWorkspaceId];
        if (existingEntry && existingEntry.repoPath === repoPath) {
            return {
                index,
                entry: existingEntry,
                changed: false,
            };
        }
    }
    const workspaceId = createWorkspaceId(index, repoPath);
    const entry = {
        workspaceId,
        repoPath,
    };
    return {
        index: {
            version: INDEX_VERSION,
            entries: {
                ...index.entries,
                [workspaceId]: entry,
            },
            repoPathToId: {
                ...index.repoPathToId,
                [repoPath]: workspaceId,
            },
        },
        entry,
        changed: true,
    };
}
function findWorkspaceEntry(index, repoPath) {
    const workspaceId = index.repoPathToId[repoPath];
    if (!workspaceId) {
        return null;
    }
    const entry = index.entries[workspaceId];
    if (!entry || entry.repoPath !== repoPath) {
        return null;
    }
    return entry;
}
function runGitCapture(cwd, args) {
    const result = spawnSync("git", args, {
        cwd,
        encoding: "utf8",
        stdio: ["ignore", "pipe", "ignore"],
        env: createGitProcessEnv(),
    });
    if (result.status !== 0 || typeof result.stdout !== "string") {
        return null;
    }
    const value = result.stdout.trim();
    return value.length > 0 ? value : null;
}
function detectGitRoot(cwd) {
    return runGitCapture(cwd, ["rev-parse", "--show-toplevel"]);
}
function detectGitCurrentBranch(repoPath) {
    return runGitCapture(repoPath, ["symbolic-ref", "--quiet", "--short", "HEAD"]);
}
function detectGitBranches(repoPath) {
    // TODO: support showing remote branches again once worktree creation can safely fetch/pull
    // and resolve missing local tracking branches automatically.
    const output = runGitCapture(repoPath, ["for-each-ref", "--format=%(refname:short)", "refs/heads"]);
    if (!output) {
        return [];
    }
    const unique = new Set();
    for (const line of output.split("\n")) {
        const trimmed = line.trim();
        if (!trimmed || trimmed === "HEAD") {
            continue;
        }
        unique.add(trimmed);
    }
    return Array.from(unique).sort((left, right) => left.localeCompare(right));
}
function detectGitDefaultBranch(repoPath, branches) {
    const remoteHead = runGitCapture(repoPath, ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]);
    if (remoteHead) {
        const normalized = remoteHead.startsWith("origin/") ? remoteHead.slice("origin/".length) : remoteHead;
        if (normalized) {
            return normalized;
        }
    }
    if (branches.includes("main")) {
        return "main";
    }
    if (branches.includes("master")) {
        return "master";
    }
    return branches[0] ?? null;
}
function detectGitRepositoryInfo(repoPath) {
    const gitRoot = detectGitRoot(repoPath);
    if (!gitRoot) {
        throw new Error(`No git repository detected at ${repoPath}`);
    }
    const currentBranch = detectGitCurrentBranch(repoPath);
    const branches = detectGitBranches(repoPath);
    const orderedBranches = currentBranch && !branches.includes(currentBranch) ? [currentBranch, ...branches] : branches;
    const defaultBranch = detectGitDefaultBranch(repoPath, orderedBranches);
    return {
        currentBranch,
        defaultBranch,
        branches: orderedBranches,
    };
}
async function resolveWorkspacePath(cwd) {
    const resolvedCwd = resolve(cwd);
    let canonicalCwd = resolvedCwd;
    try {
        canonicalCwd = await realpath(resolvedCwd);
    }
    catch {
        canonicalCwd = resolvedCwd;
    }
    const gitRoot = detectGitRoot(canonicalCwd);
    if (!gitRoot) {
        throw new Error(`No git repository detected at ${canonicalCwd}`);
    }
    const resolvedGitRoot = resolve(gitRoot);
    try {
        return await realpath(resolvedGitRoot);
    }
    catch {
        return resolvedGitRoot;
    }
}
function toWorkspaceStateResponse(context, board, sessions, revision) {
    return {
        repoPath: context.repoPath,
        statePath: context.statePath,
        git: context.git,
        board,
        sessions,
        revision,
    };
}
export class WorkspaceStateConflictError extends Error {
    currentRevision;
    constructor(expectedRevision, currentRevision) {
        super(`Workspace state revision mismatch: expected ${expectedRevision}, current ${currentRevision}.`);
        this.name = "WorkspaceStateConflictError";
        this.currentRevision = currentRevision;
    }
}
export async function loadWorkspaceContext(cwd, options = {}) {
    const repoPath = await resolveWorkspacePath(cwd);
    const autoCreateIfMissing = options.autoCreateIfMissing ?? true;
    if (!autoCreateIfMissing) {
        const index = await readWorkspaceIndex();
        const existingEntry = findWorkspaceEntry(index, repoPath);
        if (!existingEntry) {
            throw new Error(`Project ${repoPath} is not added to Kanban yet.`);
        }
        return {
            repoPath,
            workspaceId: existingEntry.workspaceId,
            statePath: getWorkspaceDirectoryPath(existingEntry.workspaceId),
            git: detectGitRepositoryInfo(repoPath),
        };
    }
    return await lockedFileSystem.withLock(getWorkspaceIndexLockRequest(), async () => {
        let index = await readWorkspaceIndex();
        const existingEntry = findWorkspaceEntry(index, repoPath);
        const ensured = existingEntry
            ? { index, entry: existingEntry, changed: false }
            : ensureWorkspaceEntry(index, repoPath);
        index = ensured.index;
        if (ensured.changed) {
            await writeWorkspaceIndex(index);
        }
        return {
            repoPath,
            workspaceId: ensured.entry.workspaceId,
            statePath: getWorkspaceDirectoryPath(ensured.entry.workspaceId),
            git: detectGitRepositoryInfo(repoPath),
        };
    });
}
export async function loadWorkspaceContextById(workspaceId) {
    const index = await readWorkspaceIndex();
    const entry = index.entries[workspaceId];
    if (!entry) {
        return null;
    }
    try {
        return await loadWorkspaceContext(entry.repoPath);
    }
    catch {
        return null;
    }
}
export async function listWorkspaceIndexEntries() {
    const index = await readWorkspaceIndex();
    return Object.values(index.entries)
        .map((entry) => ({
        workspaceId: entry.workspaceId,
        repoPath: entry.repoPath,
    }))
        .sort((left, right) => left.repoPath.localeCompare(right.repoPath));
}
export async function removeWorkspaceIndexEntry(workspaceId) {
    return await lockedFileSystem.withLock(getWorkspaceIndexLockRequest(), async () => {
        const index = await readWorkspaceIndex();
        const entry = index.entries[workspaceId];
        if (!entry) {
            return false;
        }
        delete index.entries[workspaceId];
        delete index.repoPathToId[entry.repoPath];
        await writeWorkspaceIndex(index);
        return true;
    });
}
export async function removeWorkspaceStateFiles(workspaceId) {
    await lockedFileSystem.withLocks([getWorkspacesRootLockRequest(), getWorkspaceDirectoryLockRequest(workspaceId)], async () => {
        await rm(getWorkspaceDirectoryPath(workspaceId), {
            recursive: true,
            force: true,
        });
    });
}
export async function loadWorkspaceState(cwd) {
    const context = await loadWorkspaceContext(cwd);
    const board = await readWorkspaceBoard(context.workspaceId);
    const sessions = await readWorkspaceSessions(context.workspaceId);
    const meta = await readWorkspaceMeta(context.workspaceId);
    return toWorkspaceStateResponse(context, board, sessions, meta.revision);
}
export async function saveWorkspaceState(cwd, payload) {
    const parsedPayload = parseWorkspaceStateSavePayload(payload);
    const context = await loadWorkspaceContext(cwd);
    return await lockedFileSystem.withLock(getWorkspaceDirectoryLockRequest(context.workspaceId), async () => {
        const metaPath = getWorkspaceMetaPath(context.workspaceId);
        const currentMeta = await readWorkspaceMeta(context.workspaceId);
        const expectedRevision = parsedPayload.expectedRevision;
        if (typeof expectedRevision === "number" &&
            Number.isInteger(expectedRevision) &&
            expectedRevision >= 0 &&
            expectedRevision !== currentMeta.revision) {
            throw new WorkspaceStateConflictError(expectedRevision, currentMeta.revision);
        }
        const board = parsedPayload.board;
        const sessions = parsedPayload.sessions;
        const nextRevision = currentMeta.revision + 1;
        const nextMeta = {
            revision: nextRevision,
            updatedAt: Date.now(),
        };
        await lockedFileSystem.writeJsonFileAtomic(getWorkspaceBoardPath(context.workspaceId), board, {
            lock: null,
        });
        await lockedFileSystem.writeJsonFileAtomic(getWorkspaceSessionsPath(context.workspaceId), sessions, {
            lock: null,
        });
        await lockedFileSystem.writeJsonFileAtomic(metaPath, nextMeta, {
            lock: null,
        });
        return toWorkspaceStateResponse(context, board, sessions, nextRevision);
    });
}
export async function mutateWorkspaceState(cwd, mutate) {
    const context = await loadWorkspaceContext(cwd);
    return await lockedFileSystem.withLock(getWorkspaceDirectoryLockRequest(context.workspaceId), async () => {
        const currentBoard = await readWorkspaceBoard(context.workspaceId);
        const currentSessions = await readWorkspaceSessions(context.workspaceId);
        const currentMeta = await readWorkspaceMeta(context.workspaceId);
        const currentState = toWorkspaceStateResponse(context, currentBoard, currentSessions, currentMeta.revision);
        const mutation = mutate(currentState);
        if (mutation.save === false) {
            return {
                value: mutation.value,
                state: currentState,
                saved: false,
            };
        }
        const nextBoard = mutation.board;
        const nextSessions = mutation.sessions ?? currentSessions;
        const nextRevision = currentMeta.revision + 1;
        const nextMeta = {
            revision: nextRevision,
            updatedAt: Date.now(),
        };
        await lockedFileSystem.writeJsonFileAtomic(getWorkspaceBoardPath(context.workspaceId), nextBoard, {
            lock: null,
        });
        await lockedFileSystem.writeJsonFileAtomic(getWorkspaceSessionsPath(context.workspaceId), nextSessions, {
            lock: null,
        });
        await lockedFileSystem.writeJsonFileAtomic(getWorkspaceMetaPath(context.workspaceId), nextMeta, {
            lock: null,
        });
        return {
            value: mutation.value,
            state: toWorkspaceStateResponse(context, nextBoard, nextSessions, nextRevision),
            saved: true,
        };
    });
}
//# sourceMappingURL=workspace-state.js.map
//# debugId=726b8dc0-c40b-5fd0-bd16-d1fc5a223193
