{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/workspace/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Workspace Intelligence public facade.\n *\n * Provides a single entry point for building, refreshing, querying and\n * diagnosing a durable workspace index. The index is always a disposable,\n * rebuildable projection of the authoritative current workspace.\n */\n\nimport { mkdirSync, rmSync } from \"node:fs\";\nimport { embeddingSummary, resolveIndexConfig, type WorkspaceIndexConfig } from \"./config.js\";\nimport { resolveEmbeddingBackend } from \"./embedding.js\";\nimport { type ResolvedWorkspaceIdentity, resolveWorkspaceIdentity, workspaceIndexDir } from \"./identity.js\";\nimport { type BuildOptions, type BuildReport, WorkspaceIndexer } from \"./indexer.js\";\nimport { planQuery } from \"./planner.js\";\nimport { executeRetrieval, revalidateResult } from \"./retrieve.js\";\nimport { WorkspaceDb } from \"./storage.js\";\nimport type { EmbeddingBackend, RetrievalPlan, WorkspaceRetrievalResult } from \"./types.js\";\nimport { VectorIndex } from \"./vectors.js\";\n\nexport interface IndexStatus {\n\tworkspaceId: string;\n\troot: string;\n\tstorageDir: string;\n\thasReadyGeneration: boolean;\n\tcurrentGeneration?: string;\n\tgenerationCount: number;\n\tschemaVersion: number;\n\tembedding: ReturnType<typeof embeddingSummary>;\n\tfileCount?: number;\n\tchunkCount?: number;\n\tsymbolCount?: number;\n\tembeddingCount?: number;\n\tfreshnessCheck?: \"current\" | \"possibly_stale\" | \"stale\" | \"unknown\";\n}\n\nexport interface SearchOptions {\n\tquery: string;\n\tmode?: \"lexical\" | \"semantic\" | \"symbol\" | \"hybrid\" | \"path\";\n\tlimit?: number;\n\tlanguageFilters?: string[];\n\tfileClassFilters?: string[];\n\tpathFilter?: string;\n\tcurrentFile?: string;\n\tcurrentSymbol?: string;\n\tfreshOnly?: boolean;\n\ttaskRole?: string;\n}\n\nexport class WorkspaceIndex {\n\treadonly identity: ResolvedWorkspaceIdentity;\n\treadonly config: WorkspaceIndexConfig;\n\treadonly storageDir: string;\n\tprivate db: WorkspaceDb;\n\tprivate embedding?: EmbeddingBackend;\n\n\tconstructor(cwd: string, config?: Partial<WorkspaceIndexConfig>) {\n\t\tthis.config = resolveIndexConfig(config);\n\t\tthis.identity = resolveWorkspaceIdentity(cwd);\n\t\tthis.storageDir = workspaceIndexDir(this.config.storageRoot ?? \"\", this.identity.workspaceId);\n\t\tmkdirSync(this.storageDir, { recursive: true });\n\t\tthis.db = WorkspaceDb.open(this.storageDir, this.identity.workspaceId);\n\t}\n\n\tclose(): void {\n\t\tthis.db.close();\n\t}\n\n\t/** Resolve the configured embedding backend, or a supplied override for tests. */\n\tprivate getEmbedding(override?: EmbeddingBackend): EmbeddingBackend {\n\t\tif (override) return override;\n\t\tif (!this.embedding) this.embedding = resolveEmbeddingBackend(this.config.embedding);\n\t\treturn this.embedding;\n\t}\n\n\tasync build(opts: BuildOptions = {}): Promise<BuildReport> {\n\t\tconst indexer = new WorkspaceIndexer(this.db, this.identity, this.config);\n\t\treturn indexer.build({ ...opts, embedBackend: this.getEmbedding(opts.embedBackend) });\n\t}\n\n\tasync refresh(opts: BuildOptions = {}): Promise<BuildReport> {\n\t\tconst indexer = new WorkspaceIndexer(this.db, this.identity, this.config);\n\t\treturn indexer.refresh({ ...opts, embedBackend: this.getEmbedding(opts.embedBackend) });\n\t}\n\n\tstatus(): IndexStatus {\n\t\tconst ready = this.db.currentReadyGeneration();\n\t\tconst counts = ready ? this.db.counts(ready.generationId) : undefined;\n\t\tconst build = this.db.currentBuildingGeneration();\n\t\treturn {\n\t\t\tworkspaceId: this.identity.workspaceId,\n\t\t\troot: this.identity.canonicalRoot,\n\t\t\tstorageDir: this.storageDir,\n\t\t\thasReadyGeneration: Boolean(ready),\n\t\t\tcurrentGeneration: ready?.generationId,\n\t\t\tgenerationCount: this.db.listGenerations().length,\n\t\t\tschemaVersion: this.config ? 1 : 1,\n\t\t\tembedding: embeddingSummary(this.config),\n\t\t\tfileCount: counts?.files,\n\t\t\tchunkCount: counts?.chunks,\n\t\t\tsymbolCount: counts?.symbols,\n\t\t\tembeddingCount: counts?.embeddings,\n\t\t\tfreshnessCheck: build ? \"possibly_stale\" : ready ? \"current\" : \"unknown\",\n\t\t};\n\t}\n\n\tgenerations(): Array<{\n\t\tgenerationId: string;\n\t\tstatus: string;\n\t\tcreatedAt: string;\n\t\tfileCount: number;\n\t\tchunkCount: number;\n\t}> {\n\t\treturn this.db.listGenerations().map((g) => ({\n\t\t\tgenerationId: g.generationId,\n\t\t\tstatus: g.status,\n\t\t\tcreatedAt: g.createdAt,\n\t\t\tfileCount: g.fileCount,\n\t\t\tchunkCount: g.chunkCount,\n\t\t}));\n\t}\n\n\tinspectGeneration(generationId: string): object | undefined {\n\t\tconst gen = this.db.getGeneration(generationId);\n\t\tif (!gen) return undefined;\n\t\tconst counts = this.db.counts(generationId);\n\t\treturn { generation: gen, counts, manifestHash: this.db.fileManifestHash(generationId) };\n\t}\n\n\tsearch(opts: SearchOptions): { plan: RetrievalPlan; results: WorkspaceRetrievalResult[] } {\n\t\tconst embedding = this.getEmbedding();\n\t\tconst embeddingAvailable = embedding.backendId !== \"disabled\";\n\t\tconst plan = planQuery(\n\t\t\t{\n\t\t\t\tquery: opts.query,\n\t\t\t\tlanguageFilters: opts.languageFilters,\n\t\t\t\tfileClassFilters: opts.fileClassFilters,\n\t\t\t\tpathFilter: opts.pathFilter,\n\t\t\t\tcurrentFile: opts.currentFile,\n\t\t\t\tcurrentSymbol: opts.currentSymbol,\n\t\t\t\ttaskRole: opts.taskRole,\n\t\t\t\tmaximumResults: opts.limit ?? this.config.maximumResults,\n\t\t\t\tmaximumContextTokens: this.config.maximumContextTokens,\n\t\t\t},\n\t\t\t{ embeddingAvailable },\n\t\t);\n\t\t// Mode override adjusts generators when explicitly requested.\n\t\tif (opts.mode === \"lexical\") plan.generators = [{ kind: \"lexical\", limit: plan.maximumResults, filters: {} }];\n\t\telse if (opts.mode === \"symbol\") plan.generators = [{ kind: \"symbol\", limit: plan.maximumResults, filters: {} }];\n\t\telse if (opts.mode === \"path\") plan.generators = [{ kind: \"path\", limit: plan.maximumResults, filters: {} }];\n\t\telse if (opts.mode === \"semantic\") {\n\t\t\tplan.generators = embeddingAvailable\n\t\t\t\t? [{ kind: \"semantic\", limit: plan.maximumResults, filters: {} }]\n\t\t\t\t: [{ kind: \"lexical\", limit: plan.maximumResults, filters: {} }];\n\t\t}\n\n\t\tconst outcome = executeRetrieval(this.db, {\n\t\t\tplan,\n\t\t\tembedding,\n\t\t\troot: this.identity.canonicalRoot,\n\t\t\tfreshOnly: opts.freshOnly,\n\t\t});\n\t\treturn { plan, results: outcome.results };\n\t}\n\n\t/** Revalidate a result's content hash against the current file. */\n\trevalidate(result: WorkspaceRetrievalResult): WorkspaceRetrievalResult[\"freshness\"] {\n\t\treturn revalidateResult(result, this.identity.canonicalRoot);\n\t}\n\n\tverify(deep = false): { valid: boolean; issues: string[]; checks: Array<{ name: string; status: string }> } {\n\t\tconst issues: string[] = [];\n\t\tconst checks: Array<{ name: string; status: string }> = [];\n\t\ttry {\n\t\t\tthis.db.db.prepare(\"SELECT COUNT(*) c FROM files\").get();\n\t\t\tchecks.push({ name: \"database\", status: \"ok\" });\n\t\t} catch {\n\t\t\tchecks.push({ name: \"database\", status: \"corrupt\" });\n\t\t\tissues.push(\"database unreadable or corrupt\");\n\t\t\treturn { valid: false, issues, checks };\n\t\t}\n\t\tconst ready = this.db.currentReadyGeneration();\n\t\tif (!ready) {\n\t\t\tchecks.push({ name: \"ready_generation\", status: \"missing\" });\n\t\t} else {\n\t\t\tconst manifest = this.db.fileManifest(ready.generationId);\n\t\t\tconst counts = this.db.counts(ready.generationId);\n\t\t\tchecks.push({ name: \"ready_generation\", status: \"ok\" });\n\t\t\tif (manifest.size !== counts.files) issues.push(\"file manifest/count mismatch\");\n\t\t\t// Dimension consistency for vectors.\n\t\t\tconst dims = new VectorIndex(this.db).modelIdentity(ready.generationId);\n\t\t\tif (dims && (dims.dimensions <= 0 || dims.dimensions > 65536)) issues.push(\"suspicious vector dimensions\");\n\t\t\tchecks.push({ name: \"vectors\", status: dims ? \"ok\" : \"none\" });\n\t\t}\n\t\tif (deep) {\n\t\t\t// Deep: recompute manifest hash and compare to stored.\n\t\t\tconst recomputed = this.db.fileManifestHash(ready?.generationId ?? \"\");\n\t\t\tif (ready && recomputed !== ready.sourceSnapshot.fileManifestHash) {\n\t\t\t\tissues.push(\"file manifest hash mismatch (index drift)\");\n\t\t\t}\n\t\t}\n\t\treturn { valid: issues.length === 0, issues, checks };\n\t}\n\n\t/** Prune superseded/failed generations and stale data. */\n\tprune(opts: { preview?: boolean; keepAudit?: number; activeEvidence?: Set<string> }): {\n\t\tremoved: string[];\n\t\tpreview: boolean;\n\t} {\n\t\tconst keepAudit = opts.keepAudit ?? 2;\n\t\tconst ready = this.db.currentReadyGeneration();\n\t\tconst gens = this.db.listGenerations();\n\t\tconst readyOrdered = gens\n\t\t\t.filter((x) => x.status === \"ready\")\n\t\t\t.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1));\n\t\tconst removed: string[] = [];\n\t\tfor (const g of gens) {\n\t\t\tif (g.generationId === ready?.generationId) continue;\n\t\t\tif (opts.activeEvidence?.has(g.generationId)) continue;\n\t\t\tif (g.status === \"ready\") {\n\t\t\t\tconst index = readyOrdered.findIndex((x) => x.generationId === g.generationId);\n\t\t\t\tif (index >= 0 && index < keepAudit) continue; // retain recent ready audit generations\n\t\t\t}\n\t\t\tif (opts.preview) {\n\t\t\t\tremoved.push(g.generationId);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthis.deleteGenerationData(g.generationId);\n\t\t\tthis.db.db.prepare(\"DELETE FROM generations WHERE generation_id = ?\").run(g.generationId);\n\t\t\tremoved.push(g.generationId);\n\t\t}\n\t\treturn { removed, preview: Boolean(opts.preview) };\n\t}\n\n\tprivate deleteGenerationData(generationId: string): void {\n\t\tfor (const table of [\"files\", \"chunks\", \"symbols\", \"relations\", \"git_meta\", \"vectors\", \"postings\", \"lex\"]) {\n\t\t\tthis.db.db.prepare(`DELETE FROM ${table} WHERE generation_id = ?`).run(generationId);\n\t\t}\n\t}\n\n\t/** Discard all index state for the workspace (rebuild from source). */\n\tasync rebuild(opts: BuildOptions = {}): Promise<BuildReport> {\n\t\t// Leave old ready generation serving during rebuild; then supersede it.\n\t\tconst report = await this.build(opts);\n\t\tconst prev = this.db\n\t\t\t.listGenerations()\n\t\t\t.filter((g) => g.status === \"ready\" && g.generationId !== report.generationId);\n\t\tfor (const p of prev) this.db.updateGenerationStatus(p.generationId, \"superseded\");\n\t\treturn report;\n\t}\n\n\t/** Delete the entire index directory (destructive, user-invoked). */\n\tdestroy(): void {\n\t\tthis.db.close();\n\t\ttry {\n\t\t\trmSync(this.storageDir, { recursive: true, force: true });\n\t\t} catch {\n\t\t\t/* best effort */\n\t\t}\n\t}\n\n\t/** List indexed file records for a generation (bounded). */\n\tfiles(generationId?: string): Array<{ path: string; classification: string; languageId?: string }> {\n\t\tconst gen = generationId ?? this.db.currentReadyGeneration()?.generationId;\n\t\tif (!gen) return [];\n\t\tconst rows = this.db.db\n\t\t\t.prepare(\n\t\t\t\t\"SELECT path, classification, language_id FROM files WHERE generation_id = ? ORDER BY path LIMIT 5000\",\n\t\t\t)\n\t\t\t.all(gen) as Array<{ path: string; classification: string; language_id: string | null }>;\n\t\treturn rows.map((r) => ({\n\t\t\tpath: r.path,\n\t\t\tclassification: r.classification,\n\t\t\tlanguageId: r.language_id ?? undefined,\n\t\t}));\n\t}\n\n\tsymbols(generationId?: string, limit = 500): Array<{ name: string; kind: string; path: string; startLine: number }> {\n\t\tconst gen = generationId ?? this.db.currentReadyGeneration()?.generationId;\n\t\tif (!gen) return [];\n\t\tconst rows = this.db.db\n\t\t\t.prepare(\n\t\t\t\t\"SELECT s.name, s.kind, f.path, s.start_line FROM symbols s JOIN files f ON f.generation_id = s.generation_id AND f.file_id = s.file_id WHERE s.generation_id = ? ORDER BY s.name LIMIT ?\",\n\t\t\t)\n\t\t\t.all(gen, limit) as Array<{ name: string; kind: string; path: string; start_line: number }>;\n\t\treturn rows.map((r) => ({ name: r.name, kind: r.kind, path: r.path, startLine: r.start_line }));\n\t}\n\n\tstats(): { files: number; chunks: number; symbols: number; embeddings: number; byLanguage: Record<string, number> } {\n\t\tconst ready = this.db.currentReadyGeneration();\n\t\tif (!ready) return { files: 0, chunks: 0, symbols: 0, embeddings: 0, byLanguage: {} };\n\t\tconst counts = this.db.counts(ready.generationId);\n\t\tconst langRows = this.db.db\n\t\t\t.prepare(\"SELECT language_id, COUNT(*) c FROM files WHERE generation_id = ? GROUP BY language_id\")\n\t\t\t.all(ready.generationId) as Array<{ language_id: string | null; c: number }>;\n\t\tconst byLanguage: Record<string, number> = {};\n\t\tfor (const r of langRows) byLanguage[r.language_id ?? \"unknown\"] = r.c;\n\t\treturn {\n\t\t\tfiles: counts.files,\n\t\t\tchunks: counts.chunks,\n\t\t\tsymbols: counts.symbols,\n\t\t\tembeddings: counts.embeddings,\n\t\t\tbyLanguage,\n\t\t};\n\t}\n}\n\nexport type { WorkspaceIndexConfig } from \"./config.js\";\n// Re-export for convenience.\nexport { resolveWorkspaceIdentity, workspaceIndexDir } from \"./identity.js\";\nexport { executeRetrieval, type RetrieveOptions, revalidateResult } from \"./retrieve.js\";\n\n/** Doctor checks for the workspace index subsystem (read-only). */\ntype WsCheckStatus = \"pass\" | \"fail\" | \"warn\" | \"unavailable\" | \"skipped\";\nexport function workspaceDoctorChecks(\n\tcwd: string,\n): Array<{ checkId: string; component: string; status: WsCheckStatus; reasonCode: string; summary: string }> {\n\tconst checks: Array<{\n\t\tcheckId: string;\n\t\tcomponent: string;\n\t\tstatus: WsCheckStatus;\n\t\treasonCode: string;\n\t\tsummary: string;\n\t}> = [];\n\tlet index: WorkspaceIndex | undefined;\n\ttry {\n\t\tindex = new WorkspaceIndex(cwd);\n\t\tconst status = index.status();\n\t\tconst verify = index.verify();\n\t\tchecks.push({\n\t\t\tcheckId: \"index.storage\",\n\t\t\tcomponent: \"index\",\n\t\t\tstatus: verify.valid ? \"pass\" : \"fail\",\n\t\t\treasonCode: verify.valid ? \"index_readable\" : \"index_corrupt\",\n\t\t\tsummary: verify.valid ? `index storage ${status.storageDir}` : verify.issues.join(\"; \"),\n\t\t});\n\t\tchecks.push({\n\t\t\tcheckId: \"index.ready_generation\",\n\t\t\tcomponent: \"index\",\n\t\t\tstatus: status.hasReadyGeneration ? \"pass\" : \"warn\",\n\t\t\treasonCode: status.hasReadyGeneration ? \"ready_generation_present\" : \"no_ready_generation\",\n\t\t\tsummary: status.hasReadyGeneration\n\t\t\t\t? `generation ${status.currentGeneration} files=${status.fileCount} chunks=${status.chunkCount} symbols=${status.symbolCount}`\n\t\t\t\t: \"no ready generation — run `jensen index build`\",\n\t\t});\n\t\tchecks.push({\n\t\t\tcheckId: \"index.embedding_mode\",\n\t\t\tcomponent: \"embeddings\",\n\t\t\tstatus: status.embedding.mode === \"disabled\" ? \"skipped\" : \"pass\",\n\t\t\treasonCode: `embedding_mode_${status.embedding.mode}`,\n\t\t\tsummary: `embedding mode=${status.embedding.mode} local=${status.embedding.local} model=${status.embedding.modelId ?? \"n/a\"} dims=${status.embedding.dimensions ?? \"n/a\"}`,\n\t\t});\n\t\tchecks.push({\n\t\t\tcheckId: \"index.freshness\",\n\t\t\tcomponent: \"index\",\n\t\t\tstatus:\n\t\t\t\tstatus.freshnessCheck === \"current\"\n\t\t\t\t\t? \"pass\"\n\t\t\t\t\t: status.freshnessCheck === \"possibly_stale\"\n\t\t\t\t\t\t? \"warn\"\n\t\t\t\t\t\t: \"unavailable\",\n\t\t\treasonCode: `freshness_${status.freshnessCheck ?? \"unknown\"}`,\n\t\t\tsummary: `index freshness ${status.freshnessCheck ?? \"unknown\"}`,\n\t\t});\n\t} catch (error) {\n\t\tchecks.push({\n\t\t\tcheckId: \"index.storage\",\n\t\t\tcomponent: \"index\",\n\t\t\tstatus: \"fail\",\n\t\t\treasonCode: \"index_open_failed\",\n\t\t\tsummary: error instanceof Error ? error.message : String(error),\n\t\t});\n\t} finally {\n\t\tindex?.close();\n\t}\n\treturn checks;\n}\n"]}