import { type PreflightReport } from "./project-preflight.js";
export interface RegisteredProject {
    id: string;
    name: string;
    rootPath: string;
    createdAt: number;
    color?: string;
    palette?: string;
    colorLight: string;
    colorDark: string;
    position?: number;
    provisional?: boolean;
    /**
     * True for synthetic projects that should be filtered out of UI listings
     * but still resolvable by id (e.g. the "system" project used as the
     * persistence anchor for system-scope tool-assistant sessions). Hidden
     * projects must never appear in /api/projects responses.
     */
    hidden?: boolean;
}
/** Stable id for the synthetic system project. */
export declare const SYSTEM_PROJECT_ID = "system";
export type ProjectOrderErrorCode = "invalid_project_order" | "stale_project_order";
export declare class ProjectOrderError extends Error {
    readonly code: ProjectOrderErrorCode;
    readonly details: {
        expectedProjectIds?: string[];
        receivedProjectIds?: string[];
    };
    constructor(code: ProjectOrderErrorCode, message: string, details?: {
        expectedProjectIds?: string[];
        receivedProjectIds?: string[];
    });
}
/**
 * Detect whether `rootPath` resolves through a symlink to a different
 * absolute path. Best-effort — EPERM/ENOENT are swallowed and treated as
 * non-symlink.
 */
export declare function detectSymlinkRoot(rootPath: string): {
    symlink: false;
} | {
    symlink: true;
    canonical: string;
};
/**
 * Thrown by `register()` (and friends) when the supplied rootPath is a
 * symlink to a different canonical path and the caller has not opted in via
 * `acceptCanonical`. The REST surface translates this into a structured 400
 * carrying both paths so the UI can prompt the user.
 */
/**
 * Thrown when a server-side preflight pass surfaces any `fail` check. The
 * REST surface translates this into a 400 carrying the full report.
 */
export declare class PreflightFailedError extends Error {
    readonly rootPath: string;
    readonly report: PreflightReport;
    readonly code = "preflight_failed";
    constructor(rootPath: string, report: PreflightReport, failingSummary: string);
}
export declare class SymlinkProjectRootError extends Error {
    readonly rootPath: string;
    readonly canonical: string;
    readonly code = "symlink_root";
    constructor(rootPath: string, canonical: string);
}
export declare class ProjectRegistry {
    private projects;
    private readonly storePath;
    constructor(stateDir: string);
    private participatesInVisibleOrder;
    private positionSortValue;
    private compareVisibleOrderEntries;
    private normalizeVisiblePositions;
    private nextVisiblePosition;
    /** Read projects from disk. Missing file is treated as empty registry. */
    load(): void;
    /** Atomically persist projects to disk (write tmp + rename). */
    save(): void;
    /** Return all registered projects. Visible projects are ordered by position; hidden projects are appended by createdAt. */
    list(): RegisteredProject[];
    setVisibleOrder(projectIds: string[]): RegisteredProject[];
    /** Lookup by project ID. */
    get(id: string): RegisteredProject | undefined;
    /** Find a project whose rootPath matches (normalized). Excludes hidden
     * synthetic projects (e.g. "system") so that real-project lookups don't
     * accidentally match the install-dir anchor of the hidden system project. */
    getByPath(rootPath: string): RegisteredProject | undefined;
    /** Find the project whose rootPath contains the given cwd (longest match wins).
     * Excludes hidden synthetic projects — they should never match by cwd.
     *
     * Both sides are canonicalized via realpathSync (best-effort, with textual
     * fallback on EPERM/ENOENT) so a cwd reached through a symlink resolves to
     * a project registered at the canonical path (or vice versa).
     * `getByPath()` is intentionally NOT canonicalized — it is used as a
     * duplicate-path guard for `register()` and must match exactly what the
     * caller passed. */
    findByCwd(cwd: string): RegisteredProject | undefined;
    /**
     * Register a new project.
     * - Validates rootPath is absolute.
     * - Scaffolds `.bobbit/config/` and `.bobbit/state/` if they don't exist.
     * - Persists immediately.
     */
    register(name: string, rootPath: string, opts?: {
        color?: string;
        palette?: string;
        colorLight?: string;
        colorDark?: string;
        acceptCanonical?: boolean;
    }): RegisteredProject;
    /** Update mutable fields of an existing project. */
    update(id: string, updates: Partial<Pick<RegisteredProject, "name" | "color" | "rootPath" | "palette" | "colorLight" | "colorDark">>): RegisteredProject;
    /**
     * Remove a project from the registry.
     * Does NOT delete files on disk — only unregisters.
     * Callers (e.g. server.ts) should guard against removing the last remaining project.
     */
    remove(id: string): void;
    /**
     * Register the synthetic "system" project anchored at the bobbit install
     * directory. Idempotent — safe to call repeatedly. Used as the
     * `projectId` for system-scope tool-assistant sessions so they have a
     * valid persistence anchor without forcing the user to register a real
     * project. Marked `hidden: true` so callers can filter it out of UI
     * listings.
     */
    registerSystemProject(rootPath: string): RegisteredProject;
    /**
     * Register a provisional project (used by project assistant sessions).
     * Provisional projects are real persisted projects with `provisional: true`.
     * For scaffolding (Path C), the rootPath may not exist yet — skip existence check.
     * Deduplicates: if a provisional project already exists at the same rootPath, reuse it.
     */
    registerProvisional(name: string, rootPath: string): RegisteredProject;
    /**
     * Promote a provisional project to a full project.
     * Clears `provisional` flag and optionally updates the name.
     * If rootPath now exists but wasn't scaffolded, scaffold it.
     */
    promote(id: string, updates: {
        name?: string;
    }): RegisteredProject;
    /**
     * Remove a provisional project. Safety guard: throws if the project is not provisional.
     * Does NOT delete files on disk — only unregisters.
     */
    removeProvisional(id: string): void;
}
//# sourceMappingURL=project-registry.d.ts.map