/**
 * VectorService
 *
 * Manages semantic and full-text search using LanceDB.
 *
 * Tier 1 — FTS only (no API key required):
 *   All entities are indexed in LanceDB with a BM25 full-text search index on
 *   the `text` column. This is a significant upgrade over substring matching.
 *
 * Tier 2 — Hybrid semantic search (requires Voyage AI API key):
 *   When a voyage_api_key is configured, embeddings are generated via Voyage AI
 *   (voyage-3, 1024 dimensions) and stored alongside the FTS index. Searches
 *   use LanceDB hybrid mode (vector + FTS re-ranked).
 *
 * Storage: ~/.studiograph/vector/ (configurable via user.json vector_db_path)
 * Last-index timestamps: ~/.studiograph/vector/last-indexed.json
 */
import type { EntityFile } from '../core/graph.js';
export interface VectorSearchResult {
    repoName: string;
    entityType: string;
    entityId: string;
    score: number;
}
/**
 * Snapshot of vector-index health for the /api/health endpoint. Failures are
 * fire-and-forget at the call sites (we can't block writes on indexing),
 * so the operator-visible signal lives here instead. Audit (2026-05) flagged
 * the previous `catch(console.warn)` shape: a steady leak of indexing errors
 * was undetectable until search results went stale.
 */
export interface VectorIndexHealth {
    /** True when the most recent op succeeded OR no ops have run yet. */
    healthy: boolean;
    /** Number of failed ops since process start (or since reset). */
    failureCount: number;
    /** Number of in-flight ops not yet acked. Steady growth = backpressure. */
    inFlight: number;
    /** ISO timestamp of the most recent failure, if any. */
    lastFailureAt: string | null;
    /** Most recent failure message + operation, for log correlation. */
    lastFailure: {
        op: 'upsert' | 'delete';
        entity: string;
        error: string;
    } | null;
    /** Whether Voyage embeddings are configured (Tier 2 — semantic search). */
    embeddingsEnabled: boolean;
}
export declare class VectorService {
    private dbPath;
    /**
     * Legacy fallback key passed via constructor for tests or for early-
     * boot code paths that run before the secrets store is initialized.
     * Live calls prefer `tryResolve('voyage.api_key')` so values written
     * to the store via the UI / CLI take effect without a restart.
     */
    private fallbackVoyageApiKey?;
    private workspaceRoot?;
    private db?;
    private table?;
    private lastIndexedPath;
    private initialized;
    private failureCount;
    private inFlight;
    private lastFailureAt;
    private lastFailure;
    constructor(dbPath: string, voyageApiKey?: string, workspaceRoot?: string);
    /**
     * Resolve the Voyage API key fresh on each call: store > constructor
     * fallback. Lets the operator flip the key via UI / CLI without a
     * server restart.
     */
    private get voyageApiKey();
    /** Begin tracking an in-flight op. Pair with `recordSuccess`/`recordFailure`. */
    beginOp(): void;
    recordSuccess(): void;
    recordFailure(op: 'upsert' | 'delete', entity: string, err: unknown): void;
    /**
     * Snapshot for /api/health. Healthy = the last op succeeded (or none have
     * run yet) AND there is no abnormal in-flight backlog. The failure count
     * is informational; a single transient Voyage 503 shouldn't permanently
     * mark the service unhealthy, but operators can see the trend.
     */
    getHealth(): VectorIndexHealth;
    /**
     * For asset entities backed by a PDF, the binary's extracted text
     * lives in a sidecar at `<workspace>/.studiograph/assets-text/asset/<id>.txt`
     * (written at upload time by `uploadAsset`). Returns the contents so
     * `buildEntityText` can fold the PDF into the embedding/FTS input. The
     * markdown body of an asset entity is intentionally light — without
     * this, PDF assets would be effectively invisible to RAG.
     *
     * Best-effort: missing sidecar / read errors return null. Asset stays
     * indexed by frontmatter alone.
     */
    private readPdfSidecar;
    /**
     * Open/create the LanceDB table and ensure the FTS index exists.
     * Call once before using the service.
     */
    init(): Promise<void>;
    /**
     * Index or re-index a single entity.
     * Safe to call on create, update, or as part of bulk indexing.
     */
    upsert(repoName: string, entity: EntityFile): Promise<void>;
    /**
     * Bulk upsert — used by `studiograph index`. Batches Voyage API calls.
     */
    upsertMany(repoName: string, entities: EntityFile[], opts?: {
        /** Called after each embedding batch with the cumulative number of
         *  entities processed so far inside THIS upsertMany call. Lets the
         *  caller render a progress bar during long first-time indexes. */
        onProgress?: (done: number, total: number) => void;
    }): Promise<void>;
    /**
     * Remove an entity from the index.
     */
    delete(repoName: string, entityType: string, entityId: string): Promise<void>;
    /**
     * Remove an entity by `(repo, id)` alone, without needing its type.
     *
     * Used by the serve-boot incremental indexer: when git diff reports a
     * deletion, the file is gone from disk so its type can't be resolved
     * from frontmatter. The vector store has a discrete `entity_id` column
     * so we can match on `repo + entity_id` without falling back to a LIKE
     * pattern on the synthetic id.
     *
     * Safe across type renames: a single id that legitimately had two type
     * rows (e.g. type changed mid-edit and both old + new still exist) gets
     * both wiped — which is the right behavior for incremental rebuild
     * since the next upsert recreates the current type's row.
     */
    deleteByRepoAndId(repoName: string, entityId: string): Promise<void>;
    /**
     * Search the index.
     * Uses hybrid search (vector + FTS) when Voyage key is configured, FTS only otherwise.
     */
    search(query: string, options?: {
        limit?: number;
        repos?: string[];
    }): Promise<VectorSearchResult[]>;
    /**
     * Get the last time a repo's entities were indexed.
     */
    getLastIndexedTime(repoName: string): string | undefined;
    /**
     * Record the last index time for a repo.
     *
     * Production callers omit `isoTimestamp` and the current time is used.
     * Tests pass an explicit ISO timestamp so they can rewind the clock and
     * assert the git-log `--since` window picks up the right commits.
     */
    setLastIndexedTime(repoName: string, isoTimestamp?: string): void;
    /** Whether Voyage AI embeddings are enabled (Tier 2). */
    get hasEmbeddings(): boolean;
    private generateEmbedding;
    private generateEmbeddingsBatch;
}
/**
 * Index (or re-index) all entities across workspace repos into LanceDB.
 * Standalone function — does not require a WorkspaceManager instance.
 * Used by `join`, `commit`, and other CLI commands that land entities on disk.
 *
 * @returns Total number of entities indexed.
 */
export declare function reindexWorkspace(workspacePath: string, repos: Array<{
    name: string;
    path: string;
}>, onProgress?: (msg: string) => void): Promise<number>;
/**
 * Incremental reindex for the `serve` boot path.
 *
 * First run (no last-indexed timestamps): full index of every repo, same
 * as `reindexWorkspace` but reuses the WorkspaceManager's existing graphs
 * instead of constructing new ones. Subsequent runs: diff each repo via
 * `git log --name-status` since the last indexed timestamp, then upsert /
 * delete only the changed entities. Lifted from `studiograph start`'s
 * private `initSearchIndex` so the logic survives `start.ts` deletion
 * (Phase C). No spinner — boot path uses a logger callback.
 */
/**
 * Structural slice of `WorkspaceManager` that `incrementalReindexWorkspace`
 * touches. Declared locally to avoid a vector-service → workspace-manager
 * import cycle. `getGraph`'s entity-type parameter is typed loosely (any)
 * because WorkspaceManager.getGraph().get accepts a narrow EntityType
 * union — accepting `any` here keeps the parameter contravariantly
 * compatible without dragging the validation module into vector-service.
 */
interface ReindexWorkspaceManager {
    vectorService?: VectorService;
    getGraph: (repoName: string) => {
        list: () => EntityFile[];
        get: (type: any, id: string) => EntityFile;
        /** Resolve an entity by ID alone — needed by the incremental indexer
         *  when the path's type segment isn't reliably present (flat layout,
         *  hyphenated types, arbitrary nested folders per STU-131). */
        getById: (id: string) => EntityFile;
    };
}
export declare function incrementalReindexWorkspace(workspaceManager: ReindexWorkspaceManager, repos: Array<{
    name: string;
    path: string;
}>, workspacePath: string, opts?: {
    log?: (msg: string) => void;
    /** Fired during first-time indexes to drive a progress bar in the
     *  caller. `done` is cumulative across all first-time repos, `total`
     *  is the workspace-wide first-time count. Not fired for the
     *  incremental (git-diff) path — that path is typically too small
     *  to need progress UI. */
    onFirstTimeProgress?: (done: number, total: number) => void;
}): Promise<{
    total: number;
    fresh: boolean;
}>;
/**
 * Build a text representation of an entity for FTS/embedding.
 * Combines entity type, key frontmatter fields, and markdown body.
 *
 * `extraText` is appended after the body — used by VectorService to
 * fold PDF sidecar text into asset-entity embeddings. Kept as a plain
 * string parameter (not file IO) so this stays pure and unit-testable.
 *
 * Exported for testing.
 */
export declare function buildEntityText(entity: EntityFile, extraText?: string): string;
export {};
