/**
 * Workspace Manager
 *
 * Coordinates multiple repository graph managers.
 * Provides cross-repo search, wikilink validation, and unified operations.
 */
import { BaseGraphManager, type EntityFile, type SearchOptions as BaseSearchOptions } from './graph.js';
import { WorkspaceConfig, RepoConfig } from './types.js';
import { GitUser } from '../services/git.js';
import { EntityType } from './validation.js';
import { AssetIndexService } from '../services/assets/asset-index-service.js';
import { VectorService } from '../services/vector-service.js';
import { SchemaRegistry } from './schema-registry.js';
export interface WorkspaceSearchOptions extends BaseSearchOptions {
    repoNames?: string[];
    /**
     * `keyword` skips the Voyage embedding round-trip and the LanceDB vector
     * path entirely — goes straight to in-memory name/field matching. Use for
     * autosuggest, where the user is typing a prefix and semantic similarity
     * is both useless and too slow (Voyage call ~200–800ms per keystroke).
     * Defaults to `semantic` so full-page search keeps hybrid ranking.
     */
    mode?: 'keyword' | 'semantic';
}
export interface CrossRepoEntityFile extends EntityFile {
    repoName: string;
}
export declare class WorkspaceManager {
    private workspacePath;
    private assetIndex;
    private config;
    private graphs;
    private gitUser;
    vectorService?: VectorService;
    private schemaRegistry;
    constructor(workspacePath: string, config: WorkspaceConfig, gitUser: GitUser);
    /** Absolute filesystem path to the workspace root. */
    getWorkspacePath(): string;
    /**
     * Get the workspace SchemaRegistry (includes custom extension types and field defs)
     */
    getSchemaRegistry(): SchemaRegistry;
    /**
     * Check if a repo is personal (auto-provisioned per-user; access-controlled
     * and skipped from the routine member push/pull, but committed to its own git
     * repo like any other folder — an admin/owner clone pulls it down).
     */
    isPersonalRepo(repoName: string): boolean;
    /**
     * Ensure a personal folder's directory exists and has an initial commit in
     * its own git repo. Personal folders are committed like any other folder; we
     * do NOT add them to a workspace-root .gitignore (see registerRepo — that
     * entry was always inert and misleading).
     */
    private ensurePersonalRepo;
    /**
     * Initialize graph managers for all configured repos
     */
    private initializeGraphs;
    /**
     * Build the AssetConfig for a graph manager. Picks `r2` when both
     * account_id and bucket resolve (via the store first, falling back
     * to workspace.json:r2_config); falls back to `local` otherwise.
     *
     * Shared between initializeGraphs and reloadConfig so the resolution
     * logic stays in one place — pre-2026-05-12 the two call sites had
     * drifted enough that the store-vs-file precedence diverged.
     */
    private buildAssetConfig;
    /**
     * Re-read workspace.json and initialize graphs for any newly added folders.
     * Called after creating a folder so the workspace manager picks it up
     * without a full server restart.
     */
    reloadConfig(): void;
    /** Workspace-global asset ownership index (immutable-keyed assets). */
    getAssetIndex(): AssetIndexService;
    /**
     * Install structural-mutation callbacks on every graph (current + future).
     * Called once at server startup by the Yjs persistence wiring so the
     * snapshot store stays in sync with rename / delete / type change.
     *
     * Stored on the WM so reloadConfig (which builds new graphs for newly-
     * added repos at runtime) can apply the same callbacks without a server
     * restart.
     */
    setPersistenceCallbacks(callbacks: import('./graph.js').GraphPersistenceCallbacks): void;
    /**
     * Install the body-write coordinator on every graph (current + future).
     * Routes every body write (autosave + server-write) through a single
     * critical section with assertWritable, per-doc lock, base-hash
     * precondition, atomic disk write, commit, broadcast, and
     * mdRevisionHash bookkeeping.
     */
    setBodyWriteCoordinator(coordinator: import('../server/body-write-coordinator.js').BodyWriteCoordinator): void;
    private persistenceCallbacks?;
    private bodyWriteCoordinator?;
    /**
     * Get graph manager for a specific repo.
     */
    getGraph(repoName: string): BaseGraphManager;
    /**
     * Get all repository names
     */
    getRepoNames(): string[];
    /**
     * Get repository configuration
     */
    getRepoConfig(repoName: string): RepoConfig | undefined;
    /**
     * Get the personal folder auto-provisioned for a specific user.
     */
    getUserPersonalRepo(userId: number): RepoConfig | undefined;
    /**
     * Get all repository configurations
     */
    getAllRepoConfigs(): RepoConfig[];
    /**
     * Mint/read the stable sidecar id for a folder (or the repo root), committing
     * the sidecar if a new id is minted. Saved views key folder scopes on this id
     * so they survive folder rename / move (D2).
     */
    ensureFolderId(repoName: string, folderPath: string, user?: GitUser): string;
    /**
     * Resolve a stable folder id to its `{ repo, folderPath }` across all repos,
     * or null when the id no longer maps to any folder (e.g. the folder was
     * deleted). `folderPath` is `''` for a repo root.
     */
    resolveFolderById(id: string): {
        repo: string;
        folderPath: string;
    } | null;
    /**
     * Build a single `folderId → { repo, folderPath }` index across all repos in
     * one pass (each repo's folder tree walked once). Use this when resolving many
     * folder ids in one request — e.g. enriching the caller's saved views — so the
     * cost is O(repos) tree walks instead of O(views × repos) (one
     * `resolveFolderById` per view each re-walking every repo).
     */
    buildFolderIdIndex(): Map<string, {
        repo: string;
        folderPath: string;
    }>;
    /**
     * Content/knowledge repos only — excludes `system` folders (the reserved
     * "Workspace Assets" store). Use this for knowledge surfaces that should
     * not see the app-managed asset folder: the agent folder inventory +
     * list_repos, graph maintenance, lint, and orphan detection. The asset
     * manager and authz checks (getAccessibleRepos / hasRepoAccess) keep
     * seeing system folders so assets stay listable + referenceable. See
     * ~/.claude/plans/workspace-asset-rearchitecture.md.
     */
    getContentRepoConfigs(): RepoConfig[];
    /**
     * Read-only view of the live workspace config. Use this for lookups that
     * need fields beyond repos/r2 (e.g. `railway`, `team_name`). Mutations
     * go through dedicated methods so the in-memory copy and `workspace.json`
     * stay in sync.
     */
    getConfig(): Readonly<WorkspaceConfig>;
    /**
     * Search across all repositories.
     * Uses LanceDB vector/FTS search when available, keyword search as fallback.
     */
    search(options: WorkspaceSearchOptions): Promise<CrossRepoEntityFile[]>;
    /**
     * Keyword search across repos (original implementation).
     *
     * mode=keyword (autosuggest) forwards `nameOnly: true` so each repo
     * skips the per-entity body content scan.
     */
    private keywordSearch;
    /**
     * List entities across all repositories
     */
    list(entityType?: EntityType, limit?: number): CrossRepoEntityFile[];
    /**
     * Find entity across all repositories
     */
    findEntity(entityType: EntityType, entityId: string): CrossRepoEntityFile | null;
    /**
     * Find entity by (type, id) across all repositories. Use this when you
     * have BOTH the type and the id (e.g., the asset route's URL params)
     * — entity_id is unique within (repo, entity_type) but NOT across the
     * whole workspace. A `case-study` in `portfolio` and a `reference` in
     * `analogous-experiences` can both have id `actively-learn-website`;
     * the type-blind {@link findEntityById} returns whichever repo iterates
     * first and breaks consumers that need a specific type.
     */
    findEntityByTypeAndId(entityType: string, entityId: string): CrossRepoEntityFile | null;
    /**
     * Find entity by ID across all repositories, regardless of entity type.
     * Used for workspace-level deduplication checks (where the question is
     * "is this id taken anywhere"). When you have both a type and an id,
     * prefer {@link findEntityByTypeAndId} — see the bug history note
     * there for why this matters.
     */
    findEntityById(entityId: string): CrossRepoEntityFile | null;
    /**
     * Validate wikilinks across all repositories
     */
    validateWikilinks(repoName: string, entityType: EntityType, entityId: string): {
        valid: string[];
        broken: string[];
        crossRepo: Array<{
            link: string;
            repo: string;
        }>;
    };
    /**
     * Get entities related to a given entity (cross-repo)
     */
    getRelated(repoName: string, entityType: EntityType, entityId: string): CrossRepoEntityFile[];
    /**
     * Get backlinks across all repositories
     */
    getBacklinks(repoName: string, entityType: EntityType, entityId: string): CrossRepoEntityFile[];
    /**
     * Get workspace summary statistics
     */
    getSummary(): {
        totalRepos: number;
        totalEntities: number;
        entitiesByType: Record<string, number>;
        entitiesByRepo: Record<string, number>;
    };
    /**
     * Discover repos in workspace (find .studiograph marker files)
     */
    static discoverRepos(workspacePath: string): Array<{
        name: string;
        path: string;
        type: string;
    }>;
}
