/**
 * Repository Manager
 *
 * Manages GitNexus index storage in .gitnexus/ at repo root.
 * Also maintains a global registry at ~/.gitnexus/registry.json
 * so the MCP server can discover indexed repos from any cwd.
 */
/**
 * Normalise a repo path for registry comparison across platforms
 * (#664 review feedback from @evander-wang).
 *
 * Why this exists: `path.resolve` alone is NOT enough for
 * cross-platform registry stability.
 *   - **macOS**: tmpdirs and `/var` are symlinks to `/private/var`.
 *     A child process that stored `/private/var/folders/.../repo` in
 *     the registry cannot later be matched by an outer caller that
 *     supplies the symlink form `/var/folders/.../repo`. `path.resolve`
 *     does not follow symlinks; `realpathSync.native` does.
 *   - **Windows**: GitHub runners surface tmpdirs in 8.3 short-name
 *     form (`RUNNERA~1\...`), but `process.cwd()` often returns the
 *     long form (`runneradmin\...`). `realpathSync.native` normalises
 *     both sides to the long-name canonical path.
 *
 * Fallback behaviour: if the path does not exist on disk (e.g. a user
 * passed `gitnexus remove some-alias` and the alias misses every
 * registry entry, or the caller is resolving a path that was deleted
 * after registration), we return `path.resolve(p)` rather than
 * throwing. This preserves the idempotent-on-missing semantics of
 * `resolveRegistryEntry` / `remove`.
 *
 * Backwards compatibility: this function is applied to BOTH the
 * caller-supplied input AND each stored `entry.path` at compare time
 * inside `resolveRegistryEntry`, so registries written by older
 * versions (where `registerRepo` only ran `path.resolve`) still match
 * correctly. Newly-written entries are canonicalised at write time too
 * so the registry stabilises over analyze/re-analyze cycles.
 */
export declare const canonicalizePath: (p: string) => string;
export interface RepoMeta {
    repoPath: string;
    lastCommit: string;
    indexedAt: string;
    /**
     * Canonical `origin` remote URL captured at index time. Used to
     * fingerprint the same logical repo across multiple on-disk clones
     * (worktrees, agent workspaces, "clean clone for indexing"). When
     * absent (no remote configured, git unavailable, etc.) the repo is
     * treated as path-only and sibling-clone detection is skipped.
     */
    remoteUrl?: string;
    stats?: {
        files?: number;
        nodes?: number;
        edges?: number;
        communities?: number;
        processes?: number;
        embeddings?: number;
    };
    /**
     * Bumped whenever incremental-indexing invariants change in an
     * incompatible way (delete-and-rewrite logic, subgraph extraction,
     * graph-wide node handling). On mismatch, runFullAnalysis forces a
     * full rebuild rather than risk an inconsistent incremental update.
     */
    schemaVersion?: number;
    /**
     * SHA-256 of every file's content at the time of the last successful
     * indexing run. The next run computes current hashes and diffs against
     * this map to determine which files' DB rows must be replaced.
     * Map keys are repo-relative paths.
     */
    fileHashes?: Record<string, string>;
    /**
     * Crash-recovery dirty flag. Written to meta.json BEFORE any
     * destructive DB mutation in an incremental run; cleared on success
     * by overwriting meta.json. If a run crashes between, the next run
     * sees the flag and forces a full rebuild — the cheapest path back
     * to a known-good index.
     */
    incrementalInProgress?: {
        /** When the incremental run started (epoch ms). */
        startedAt: number;
        /** Number of files in the writable set, for diagnostic logs. */
        toWriteCount: number;
    };
}
/**
 * Bumped whenever incremental-indexing invariants change incompatibly.
 */
export declare const INCREMENTAL_SCHEMA_VERSION = 1;
export interface IndexedRepo {
    repoPath: string;
    storagePath: string;
    lbugPath: string;
    metaPath: string;
    meta: RepoMeta;
}
/**
 * Shape of an entry in the global registry (~/.gitnexus/registry.json)
 */
export interface RegistryEntry {
    name: string;
    path: string;
    storagePath: string;
    indexedAt: string;
    lastCommit: string;
    /** See {@link RepoMeta.remoteUrl}. Mirrored from meta at register time. */
    remoteUrl?: string;
    stats?: RepoMeta['stats'];
}
/**
 * Get the .gitnexus storage path for a repository
 */
export declare const getStoragePath: (repoPath: string) => string;
/**
 * Get paths to key storage files
 */
export declare const getStoragePaths: (repoPath: string) => {
    storagePath: string;
    lbugPath: string;
    metaPath: string;
};
/**
 * Check whether a KuzuDB index exists in the given storage path.
 * Non-destructive — safe to call from status commands.
 */
export declare const hasKuzuIndex: (storagePath: string) => Promise<boolean>;
/**
 * Clean up stale KuzuDB files after migration to LadybugDB.
 *
 * Returns:
 *   found        — true if .gitnexus/kuzu existed and was deleted
 *   needsReindex — true if kuzu existed but lbug does not (re-analyze required)
 *
 * Callers own the user-facing messaging; this function only deletes files.
 */
export declare const cleanupOldKuzuFiles: (storagePath: string) => Promise<{
    found: boolean;
    needsReindex: boolean;
}>;
/**
 * Load metadata from an indexed repo
 */
export declare const loadMeta: (storagePath: string) => Promise<RepoMeta | null>;
/**
 * Save metadata to storage.
 *
 * Atomic via tmp-file + rename (matches `saveParseCache`'s pattern). The
 * `incrementalInProgress` dirty flag travels through this file — a crash
 * mid-write would leave a corrupt `meta.json` that the next run's
 * `loadMeta` would silently treat as "no prior index", losing the dirty
 * flag and skipping the recovery full-rebuild. Write-and-rename rules
 * that out: the rename is atomic on POSIX and on Windows (`fs.rename`
 * on `node:fs/promises` uses `MoveFileEx(REPLACE_EXISTING)`), so either
 * the old or the new file is observed at every moment.
 */
export declare const saveMeta: (storagePath: string, meta: RepoMeta) => Promise<void>;
/**
 * Check if a path has a GitNexus index
 */
export declare const hasIndex: (repoPath: string) => Promise<boolean>;
/**
 * Load an indexed repo from a path
 */
export declare const loadRepo: (repoPath: string) => Promise<IndexedRepo | null>;
/**
 * Find .gitnexus by walking up from a starting path
 */
export declare const findRepo: (startPath: string) => Promise<IndexedRepo | null>;
/**
 * Keep generated index files ignored without modifying the user's root .gitignore.
 */
export declare const ensureGitNexusIgnored: (repoPath: string) => Promise<void>;
/**
 * Get the path to the global GitNexus directory
 */
export declare const getGlobalDir: () => string;
/**
 * Get the path to the global registry file
 */
export declare const getGlobalRegistryPath: () => string;
/**
 * Read the global registry. Returns empty array if not found.
 */
export declare const readRegistry: () => Promise<RegistryEntry[]>;
/**
 * Options for {@link registerRepo}. All optional — callers without any
 * disambiguation requirement can keep calling `registerRepo(path, meta)`
 * unchanged.
 */
export interface RegisterRepoOptions {
    /**
     * User-provided alias from `analyze --name <alias>` (#829). Overrides
     * the default basename-derived registry `name`. Persisted — subsequent
     * re-analyses of the same path without `--name` preserve the alias.
     */
    name?: string;
    /**
     * Allow two DIFFERENT repo paths to register under the same alias
     * (#829). Mapped from the `--allow-duplicate-name` CLI flag.
     *
     * Scope: this flag governs cross-path alias sharing only — one repo
     * path always has exactly one registry entry (and therefore exactly
     * one alias). Re-analyzing the same path with `--name Y` overwrites
     * a previous `--name X`; it does NOT create a second entry or a
     * second alias for the same path (see the upsert-by-resolved-path
     * logic in {@link registerRepo} and the
     * `re-registerRepo with a different name overrides the previous
     * alias` test in `test/unit/repo-manager.test.ts`).
     *
     * Distinct from `--force` (which only triggers pipeline re-index);
     * a user accepting a duplicate alias should not be forced to also
     * re-run the full pipeline.
     */
    allowDuplicateName?: boolean;
}
/**
 * Thrown by {@link registerRepo} when a requested name is already in
 * use by a DIFFERENT path. The CLI layer surfaces this as an actionable
 * error instead of relying on `.message` string-matching.
 *
 * The colliding alias is exposed as `err.registryName` (not `err.name`).
 * `err.name` keeps its inherited `Error.prototype.name` semantics (the
 * class name) so downstream code can do the usual `err.name ===
 * 'RegistryNameCollisionError'` checks; use the `kind` discriminant or
 * `instanceof RegistryNameCollisionError` for type-safe narrowing.
 */
export declare class RegistryNameCollisionError extends Error {
    readonly registryName: string;
    readonly existingPath: string;
    readonly requestedPath: string;
    readonly kind: "RegistryNameCollisionError";
    constructor(registryName: string, existingPath: string, requestedPath: string);
}
/**
 * Register (add or update) a repo in the global registry.
 * Called after `gitnexus analyze` completes.
 *
 * Name resolution precedence (#829, #979):
 *   1. explicit `opts.name` (from `analyze --name <alias>`)
 *   2. preserved alias on an existing entry for this path
 *   3. `git config --get remote.origin.url` repo name (#979 — recovers
 *      a meaningful name for monorepo subprojects, git worktrees, and
 *      Gas-Town-style `<rig>/refinery/rig/` layouts where the basename
 *      is generic)
 *   4. `path.basename(repoPath)` (the original default)
 *
 * Duplicate-name guard: if another path already uses the resolved
 * `name`, throw {@link RegistryNameCollisionError} unless
 * `opts.allowDuplicateName` is set. The guard ONLY fires when the user explicitly passed a
 * `name`; un-aliased basename collisions continue to register silently
 * so existing users who don't know about `--name` see no behaviour
 * change.
 *
 * Returns the `name` that was actually written to the registry — the
 * caller can re-use it to keep AGENTS.md / skill files aligned with the
 * MCP-visible repo name (#979).
 */
export declare const registerRepo: (repoPath: string, meta: RepoMeta, opts?: RegisterRepoOptions) => Promise<string>;
/**
 * Remove a repo from the global registry.
 * Called after `gitnexus clean`.
 */
export declare const unregisterRepo: (repoPath: string) => Promise<void>;
/**
 * Thrown by {@link resolveRegistryEntry} when no registered repo matches
 * the caller's target string (by alias, basename, remote-inferred name,
 * or resolved path). CLI callers that want idempotent "remove" semantics
 * should catch this and exit 0 with a warning; non-idempotent callers
 * (e.g. MCP tools) can surface the error directly.
 */
export declare class RegistryNotFoundError extends Error {
    readonly target: string;
    readonly availableNames: string[];
    readonly kind: "RegistryNotFoundError";
    constructor(target: string, availableNames: string[]);
}
/**
 * Thrown by {@link resolveRegistryEntry} when the target string matches
 * the `name` of two or more entries — only possible when the user
 * previously registered duplicates via `analyze --name X
 * --allow-duplicate-name` (#829). The error carries enough information
 * for the caller to render an actionable disambiguation hint without
 * string-matching on `.message`.
 *
 * `kind` is a string literal discriminant (same pattern as
 * {@link RegistryNameCollisionError}) so callers can narrow via
 * `err.kind === 'RegistryAmbiguousTargetError'` without importing the
 * class.
 */
export declare class RegistryAmbiguousTargetError extends Error {
    readonly target: string;
    readonly matches: RegistryEntry[];
    readonly kind: "RegistryAmbiguousTargetError";
    constructor(target: string, matches: RegistryEntry[]);
}
/**
 * Thrown by {@link assertAnalysisFinalized} when a successful `analyze`
 * run did not actually persist `meta.json` or did not register the repo
 * in `~/.gitnexus/registry.json` (#1169).
 *
 * Why this exists: on Windows, `gitnexus analyze` has been observed to
 * exit cleanly (code 0) with `lbug.wal` written but no `meta.json`,
 * leaving the repo invisible to `gitnexus list`/`status` and downstream
 * MCP discovery. The only signal to the user was an empty banner —
 * which is indistinguishable from a no-op early return. This invariant
 * fails loudly with an actionable diagnostic so the silent-finalize bug
 * surfaces with a non-zero exit code and a recoverable error message
 * regardless of the upstream root cause (re-exec churn, native module
 * side effects, antivirus, or future regressions).
 */
export declare class AnalysisNotFinalizedError extends Error {
    readonly repoPath: string;
    readonly storagePath: string;
    readonly missing: 'meta' | 'registry-entry';
    readonly registryPath: string;
    readonly kind: "AnalysisNotFinalizedError";
    constructor(repoPath: string, storagePath: string, missing: 'meta' | 'registry-entry', registryPath: string);
}
/**
 * Verify that a successful `analyze` call actually produced an indexed,
 * registered repo on disk. Two checks, both strictly required:
 *
 *   1. `meta.json` must exist at `<repoPath>/.gitnexus/meta.json`.
 *   2. The global registry (`getGlobalRegistryPath()`) must contain an
 *      entry whose canonical path matches `repoPath`.
 *
 * Throws {@link AnalysisNotFinalizedError} on the first failure with the
 * specific missing artifact. Pure read — does not mutate disk state.
 *
 * Callers must skip this assertion on the `alreadyUpToDate` early-return
 * path, where the rebuild was deliberately not run.
 */
export declare const assertAnalysisFinalized: (repoPath: string) => Promise<void>;
/**
 * Thrown by {@link assertSafeStoragePath} when a registry entry's
 * `storagePath` does NOT point at the expected `<entry.path>/.gitnexus`
 * subfolder. CLI destructive commands (`remove`, `clean --all`) should
 * catch this and exit non-zero without deleting anything — the usual
 * cause is a corrupted or hand-edited `~/.gitnexus/registry.json`, and
 * proceeding would mean `fs.rm(recursive: true)` on whatever odd path
 * the entry is pointing at.
 */
export declare class UnsafeStoragePathError extends Error {
    readonly entry: RegistryEntry;
    readonly expectedStoragePath: string;
    readonly actualStoragePath: string;
    readonly kind: "UnsafeStoragePathError";
    constructor(entry: RegistryEntry, expectedStoragePath: string, actualStoragePath: string);
}
/**
 * Guard rail for destructive CLI paths (`remove` #664,
 * `clean --all` #258, future MCP `remove` tool): verify that a
 * registry entry's `storagePath` is the canonical `<repo>/.gitnexus`
 * subfolder of its `path`. If not, throw {@link UnsafeStoragePathError}
 * so the caller exits without touching disk.
 *
 * Why this exists (#1003 review — @magyargergo):
 *   - `~/.gitnexus/registry.json` is a plain-text user-writable file.
 *     A corrupted, hand-edited, or downgrade/upgrade-racing entry
 *     could plausibly end up with `storagePath === ""` (resolves to
 *     cwd), `storagePath === path` (the repo root!), `storagePath`
 *     equal to a parent/sibling of the repo, or simply any arbitrary
 *     filesystem path.
 *   - `fs.rm(recursive: true, force: true)` on ANY of those would be
 *     a runtime disaster — at best delete the user's working tree, at
 *     worst nuke an unrelated directory tree they happen to own.
 *   - `clean` (default, cwd-scoped) is safe by construction — it
 *     re-derives storagePath from `findRepo(cwd)` and never trusts
 *     the registry field. But `clean --all` DOES iterate the registry
 *     and trust each entry's stored storagePath (same shape as
 *     `remove`), so this helper must be wired into that loop too.
 *   - `server/api.ts` recomputes storagePath from `getStoragePath(entry.path)`
 *     and so is likewise safe-by-construction.
 *
 * Pure string check — does NOT require the paths to exist on disk.
 * Windows: case-insensitive; POSIX: case-sensitive. Matches the
 * comparison shape used elsewhere in this module.
 */
export declare const assertSafeStoragePath: (entry: RegistryEntry) => void;
/**
 * Resolve a user-supplied target string (from `gitnexus remove <target>`
 * or equivalent MCP tool argument) to a single registry entry.
 *
 * Match precedence (first hit wins, subsequent tiers are only tried if
 * the prior tier produces zero matches):
 *   1. Exact resolved-path match (Windows: case-insensitive).
 *      Paths are unique by registry construction, so a path match can
 *      never be ambiguous.
 *   2. Exact `name` match (case-insensitive). If ≥ 2 entries share the
 *      name — only possible via `--allow-duplicate-name` (#829) —
 *      throws {@link RegistryAmbiguousTargetError}.
 *
 * No fuzzy / partial matching — unambiguous, scriptable behaviour is
 * more important than convenience for destructive commands.
 *
 * Throws {@link RegistryNotFoundError} if no entry matches.
 *
 * `entries` is passed in (rather than re-read) so callers that already
 * hold the registry snapshot (e.g. to print a "before" state) can avoid
 * a second disk read, and so tests can inject fixtures without touching
 * `GITNEXUS_HOME`.
 */
export declare const resolveRegistryEntry: (entries: RegistryEntry[], target: string) => RegistryEntry;
/**
 * List all registered repos from the global registry.
 * Optionally validates that each entry's .gitnexus/ still exists.
 */
export declare const listRegisteredRepos: (opts?: {
    validate?: boolean;
}) => Promise<RegistryEntry[]>;
export interface CLIConfig {
    apiKey?: string;
    model?: string;
    baseUrl?: string;
    provider?: 'openai' | 'openrouter' | 'azure' | 'custom' | 'cursor' | 'claude' | 'codex';
    cursorModel?: string;
    claudeModel?: string;
    codexModel?: string;
    /** Azure api-version query param (e.g. '2024-10-21'). Only used when provider is 'azure'. */
    apiVersion?: string;
    /** Set true when the deployment is a reasoning model (o1, o3, o4-mini). Auto-detected for OpenAI; must be set for Azure deployments. */
    isReasoningModel?: boolean;
}
/**
 * Get the path to the global CLI config file
 */
export declare const getGlobalConfigPath: () => string;
/**
 * Load CLI config from ~/.gitnexus/config.json
 */
export declare const loadCLIConfig: () => Promise<CLIConfig>;
/**
 * Save CLI config to ~/.gitnexus/config.json
 */
export declare const saveCLIConfig: (config: CLIConfig) => Promise<void>;
/**
 * Find other registered entries whose `remoteUrl` matches the given
 * one, excluding `selfPath` (case-insensitive on Windows). Entries
 * without a `remoteUrl` are ignored — we cannot prove sibling-ness
 * without a fingerprint.
 */
export declare const findSiblingClones: (remoteUrl: string | undefined, selfPath: string) => Promise<RegistryEntry[]>;
/**
 * Description of how a working directory relates to a registered index.
 *
 * `match` semantics:
 *   - `path`              — `cwd` is inside the registered entry's path.
 *   - `sibling-by-remote` — `cwd` is in a different on-disk clone of the
 *                           same repo (same `remoteUrl`).
 *   - `none`              — no relationship found.
 */
export interface CwdMatch {
    match: 'path' | 'sibling-by-remote' | 'none';
    entry?: RegistryEntry;
    /** The git toplevel of `cwd`, when `cwd` is inside a git work tree. */
    cwdGitRoot?: string;
    /** HEAD of the cwd's clone, when resolvable. */
    cwdHead?: string;
    /**
     * Number of commits the registered `lastCommit` is behind the
     * sibling-clone HEAD, when both refs are known to the cwd's clone.
     * `undefined` when the comparison cannot be performed (e.g. the
     * indexed commit isn't reachable from cwd).
     */
    drift?: number;
    /** Human-readable hint, set whenever the situation warrants warning. */
    hint?: string;
}
