@code-rag/core 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,15 +1,13 @@
1
1
  import { ok, err } from 'neverthrow';
2
2
  import { StoreError } from '../types/provider.js';
3
3
  import * as lancedb from '@lancedb/lancedb';
4
+ import { safeString, safeRecord } from '../utils/safe-cast.js';
4
5
  const TABLE_NAME = 'chunks';
5
6
  const SAFE_ID_PATTERN = /^[a-zA-Z0-9_\-:.]+$/;
6
7
  function safeParseJSON(json) {
7
8
  try {
8
9
  const parsed = JSON.parse(json);
9
- if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
10
- return parsed;
11
- }
12
- return {};
10
+ return safeRecord(parsed, {});
13
11
  }
14
12
  catch {
15
13
  return {};
@@ -18,6 +16,24 @@ function safeParseJSON(json) {
18
16
  function validateId(id) {
19
17
  return SAFE_ID_PATTERN.test(id) && id.length > 0 && id.length <= 256;
20
18
  }
19
+ /** Convert raw LanceDB toArray() rows into typed LanceDBQueryResult[]. */
20
+ function toLanceDBQueryResults(rows) {
21
+ return rows.map((row) => {
22
+ const r = safeRecord(row, {});
23
+ return {
24
+ id: safeString(r['id'], ''),
25
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Array.isArray guard; LanceDB stores vectors as number[]
26
+ vector: Array.isArray(r['vector']) ? r['vector'] : [],
27
+ content: safeString(r['content'], ''),
28
+ nl_summary: safeString(r['nl_summary'], ''),
29
+ chunk_type: safeString(r['chunk_type'], ''),
30
+ file_path: safeString(r['file_path'], ''),
31
+ language: safeString(r['language'], ''),
32
+ metadata: safeString(r['metadata'], '{}'),
33
+ _distance: typeof r['_distance'] === 'number' ? r['_distance'] : 0,
34
+ };
35
+ });
36
+ }
21
37
  export class LanceDBStore {
22
38
  storagePath;
23
39
  _dimensions;
@@ -62,15 +78,16 @@ export class LanceDBStore {
62
78
  return {
63
79
  id,
64
80
  vector: embeddings[i] ?? [],
65
- content: meta['content'] ?? '',
66
- nl_summary: meta['nl_summary'] ?? '',
67
- chunk_type: meta['chunk_type'] ?? '',
68
- file_path: meta['file_path'] ?? '',
69
- language: meta['language'] ?? '',
81
+ content: safeString(meta['content'], ''),
82
+ nl_summary: safeString(meta['nl_summary'], ''),
83
+ chunk_type: safeString(meta['chunk_type'], ''),
84
+ file_path: safeString(meta['file_path'], ''),
85
+ language: safeString(meta['language'], ''),
70
86
  metadata: JSON.stringify(meta),
71
87
  };
72
88
  });
73
- const data = rows;
89
+ // Justified cast: LanceDB API requires Record<string, unknown>[] but rows satisfy this structurally
90
+ const data = rows.map((row) => ({ ...row }));
74
91
  if (!this.table) {
75
92
  this.table = await this.db.createTable(TABLE_NAME, data);
76
93
  }
@@ -101,10 +118,11 @@ export class LanceDBStore {
101
118
  if (!this.table) {
102
119
  return ok([]);
103
120
  }
104
- const results = (await this.table
121
+ const rawResults = await this.table
105
122
  .search(embedding)
106
123
  .limit(topK)
107
- .toArray());
124
+ .toArray();
125
+ const results = toLanceDBQueryResults(rawResults);
108
126
  const mapped = results.map((row) => ({
109
127
  id: row.id,
110
128
  score: 1 / (1 + (row._distance ?? 0)),
@@ -159,6 +177,82 @@ export class LanceDBStore {
159
177
  return err(new StoreError(`LanceDB count failed: ${message}`));
160
178
  }
161
179
  }
180
+ async getById(id) {
181
+ try {
182
+ if (!validateId(id)) {
183
+ return err(new StoreError(`Invalid chunk ID: ${id}`));
184
+ }
185
+ await this.ensureConnected();
186
+ if (!this.table) {
187
+ return ok(undefined);
188
+ }
189
+ const filter = `id = '${id}'`;
190
+ const rows = (await this.table.query().where(filter).limit(1).toArray());
191
+ if (rows.length === 0) {
192
+ return ok(undefined);
193
+ }
194
+ const row = rows[0];
195
+ return ok({
196
+ id: row.id,
197
+ metadata: {
198
+ content: row.content,
199
+ nl_summary: row.nl_summary,
200
+ chunk_type: row.chunk_type,
201
+ file_path: row.file_path,
202
+ language: row.language,
203
+ ...(row.metadata ? safeParseJSON(row.metadata) : {}),
204
+ },
205
+ });
206
+ }
207
+ catch (error) {
208
+ const message = error instanceof Error ? error.message : 'Unknown error';
209
+ return err(new StoreError(`LanceDB getById failed: ${message}`));
210
+ }
211
+ }
212
+ /**
213
+ * Scan all rows from the table.
214
+ * Returns an array of { id, metadata } objects (no vectors).
215
+ * Useful for index analysis and benchmark generation.
216
+ */
217
+ async getAll(limit) {
218
+ try {
219
+ await this.ensureConnected();
220
+ if (!this.table) {
221
+ return ok([]);
222
+ }
223
+ let query = this.table.query();
224
+ if (limit !== undefined && limit > 0) {
225
+ query = query.limit(limit);
226
+ }
227
+ const rawRows = await query.toArray();
228
+ const results = rawRows.map((row) => {
229
+ const r = safeRecord(row, {});
230
+ const id = safeString(r['id'], '');
231
+ const content = safeString(r['content'], '');
232
+ const nlSummary = safeString(r['nl_summary'], '');
233
+ const chunkType = safeString(r['chunk_type'], '');
234
+ const filePath = safeString(r['file_path'], '');
235
+ const language = safeString(r['language'], '');
236
+ const metaStr = safeString(r['metadata'], '{}');
237
+ return {
238
+ id,
239
+ metadata: {
240
+ content,
241
+ nl_summary: nlSummary,
242
+ chunk_type: chunkType,
243
+ file_path: filePath,
244
+ language,
245
+ ...safeParseJSON(metaStr),
246
+ },
247
+ };
248
+ });
249
+ return ok(results);
250
+ }
251
+ catch (error) {
252
+ const message = error instanceof Error ? error.message : 'Unknown error';
253
+ return err(new StoreError(`LanceDB getAll failed: ${message}`));
254
+ }
255
+ }
162
256
  close() {
163
257
  if (this.table) {
164
258
  this.table.close();
package/dist/index.d.ts CHANGED
@@ -16,7 +16,7 @@ export type { IndexedFileState, ScannedFile, IndexerConfig, ChangeSet, IndexerRe
16
16
  export { IndexState, computeFileHash, FileScanner, ScanError, IncrementalIndexer, IndexerError, MultiRepoIndexer, MultiRepoIndexerError, checkIndexExists, FileWatcher, } from './indexer/index.js';
17
17
  export type { OllamaEmbeddingConfig, OpenAICompatibleEmbeddingConfig, QdrantConfig, ModelLifecycleConfig, BackendInfo, BackendType, GpuMode, DockerConfig, ProgressCallback, ProcessExecutor, FetchFn, } from './embedding/index.js';
18
18
  export { OllamaEmbeddingProvider, OpenAICompatibleEmbeddingProvider, ModelLifecycleManager, ModelLifecycleError, LanceDBStore, QdrantVectorStore, BM25Index, HybridSearch, } from './embedding/index.js';
19
- export type { AnalyzedQuery, QueryIntent, QueryEntity, ReadonlyGraph, RelationshipType, RelatedChunk, GraphExcerpt, ExpandedContext, TokenBudgetConfig, AssembledContext, CrossEncoderConfig, } from './retrieval/index.js';
19
+ export type { AnalyzedQuery, QueryIntent, QueryEntity, ReadonlyGraph, RelationshipType, RelatedChunk, GraphExcerpt, ExpandedContext, ChunkLookupFn, TokenBudgetConfig, AssembledContext, CrossEncoderConfig, } from './retrieval/index.js';
20
20
  export { QueryAnalyzer, ContextExpander, TokenBudgetOptimizer, CrossEncoderReRanker, ReRankerError, } from './retrieval/index.js';
21
21
  export type { ReRankerConfig, ReRanker, BacklogConfig } from './types/index.js';
22
22
  export type { BacklogItemType, BacklogItem, BacklogQuery, BacklogProvider, AzureDevOpsConfig, ClickUpConfig, JiraConfig, BacklogCodeMap, CoverageReport, } from './backlog/index.js';
@@ -29,3 +29,10 @@ export type { Role, Action, RepoAccessLevel, RepoPermission, User, AuthToken, Au
29
29
  export { ROLE_HIERARCHY, AuthError, RBACManager, OIDCProvider, SAMLProvider, AuditLogger } from './auth/index.js';
30
30
  export type { CloudStorageProvider, CloudStorageConfig, S3Config, AzureBlobConfig, GCSConfig, GCSCredentials, } from './storage/index.js';
31
31
  export { StorageError, S3StorageProvider, AzureBlobStorageProvider, GCSStorageProvider, } from './storage/index.js';
32
+ export { safeString, safeNumber, safeRecord, safeArray, safeStringUnion, } from './utils/safe-cast.js';
33
+ export type { CodeRAGRuntime, RuntimeOptions } from './runtime.js';
34
+ export { createRuntime, RuntimeError } from './runtime.js';
35
+ export type { ScannedEntity, IndexScanResult, BenchmarkQueryType, GeneratedQuery, QueryGeneratorOptions, QueryEvalResult, QueryMetrics, AggregateEvalMetrics, QueryTypeBreakdown, BenchmarkReport, BenchmarkMetadata, SearchFn, BenchmarkProgressFn, } from './benchmarks/index.js';
36
+ export { IndexScanError, parseIndexRows, buildCallerMap, buildTestMap, generateQueries, generateFindByNameQueries, generateFindByDescriptionQueries, generateFindCallersQueries, generateFindTestsQueries, generateFindImportsQueries, BenchmarkEvalError, computeQueryMetrics, computeAggregateMetrics as computeBenchmarkAggregateMetrics, computeQueryTypeBreakdown, runBenchmark, formatBenchmarkSummary, } from './benchmarks/index.js';
37
+ export { PaginationMetaSchema, ChunkSummarySchema, ChunkDetailSchema, GraphNodeSchema, GraphEdgeSchema, ViewerSearchResultSchema, EmbeddingPointSchema, ViewerStatsResponseSchema, ViewerChunksResponseSchema, ViewerChunkDetailResponseSchema, ViewerSearchResponseSchema, ViewerGraphResponseSchema, ViewerEmbeddingsResponseSchema, } from './api-contracts/index.js';
38
+ export type { ViewerPaginationMeta, ViewerChunkSummary, ViewerChunkDetail, ViewerGraphNode, ViewerGraphEdge, ViewerSearchResultType, ViewerEmbeddingPoint, ViewerStatsResponse, ViewerChunksResponse, ViewerChunkDetailResponse, ViewerSearchResponse, ViewerGraphResponse, ViewerEmbeddingsResponse, } from './api-contracts/index.js';
package/dist/index.js CHANGED
@@ -13,3 +13,8 @@ export { ConfluenceError, ConfluenceProvider, confluenceStorageToPlainText, } fr
13
13
  export { SharePointError, SharePointProvider, extractTextFromDocx, extractTextFromPdf, } from './docs/index.js';
14
14
  export { ROLE_HIERARCHY, AuthError, RBACManager, OIDCProvider, SAMLProvider, AuditLogger } from './auth/index.js';
15
15
  export { StorageError, S3StorageProvider, AzureBlobStorageProvider, GCSStorageProvider, } from './storage/index.js';
16
+ export { safeString, safeNumber, safeRecord, safeArray, safeStringUnion, } from './utils/safe-cast.js';
17
+ export { createRuntime, RuntimeError } from './runtime.js';
18
+ export { IndexScanError, parseIndexRows, buildCallerMap, buildTestMap, generateQueries, generateFindByNameQueries, generateFindByDescriptionQueries, generateFindCallersQueries, generateFindTestsQueries, generateFindImportsQueries, BenchmarkEvalError, computeQueryMetrics, computeAggregateMetrics as computeBenchmarkAggregateMetrics, computeQueryTypeBreakdown, runBenchmark, formatBenchmarkSummary, } from './benchmarks/index.js';
19
+ // --- API Contracts (shared Zod schemas for viewer REST API) ---
20
+ export { PaginationMetaSchema, ChunkSummarySchema, ChunkDetailSchema, GraphNodeSchema, GraphEdgeSchema, ViewerSearchResultSchema, EmbeddingPointSchema, ViewerStatsResponseSchema, ViewerChunksResponseSchema, ViewerChunkDetailResponseSchema, ViewerSearchResponseSchema, ViewerGraphResponseSchema, ViewerEmbeddingsResponseSchema, } from './api-contracts/index.js';
@@ -25,16 +25,18 @@ export interface ExpandedContext {
25
25
  relatedChunks: RelatedChunk[];
26
26
  graphExcerpt: GraphExcerpt;
27
27
  }
28
+ /** Callback to resolve a chunk by its ID. May be sync or async (await handles both). */
29
+ export type ChunkLookupFn = (chunkId: string) => SearchResult | undefined | Promise<SearchResult | undefined>;
28
30
  export declare class ContextExpander {
29
31
  private readonly graph;
30
32
  private readonly chunkLookup;
31
- constructor(dependencyGraph: ReadonlyGraph, chunkLookup: (chunkId: string) => SearchResult | undefined);
33
+ constructor(dependencyGraph: ReadonlyGraph, chunkLookup: ChunkLookupFn);
32
34
  /**
33
35
  * Expand search results with graph-based context.
34
36
  * For each result, walks the dependency graph to find related chunks
35
37
  * and classifies their relationships.
36
38
  */
37
- expand(results: SearchResult[], maxRelated?: number): ExpandedContext;
39
+ expand(results: SearchResult[], maxRelated?: number): Promise<ExpandedContext>;
38
40
  /**
39
41
  * Classify the relationship between two nodes based on graph edges and naming conventions.
40
42
  */
@@ -11,7 +11,7 @@ export class ContextExpander {
11
11
  * For each result, walks the dependency graph to find related chunks
12
12
  * and classifies their relationships.
13
13
  */
14
- expand(results, maxRelated = DEFAULT_MAX_RELATED) {
14
+ async expand(results, maxRelated = DEFAULT_MAX_RELATED) {
15
15
  const relatedMap = new Map();
16
16
  const graphNodes = new Set();
17
17
  const graphEdges = [];
@@ -30,7 +30,7 @@ export class ContextExpander {
30
30
  // Don't add duplicates — keep the one with shortest distance
31
31
  if (relatedMap.has(relatedId))
32
32
  continue;
33
- const relatedResult = this.chunkLookup(relatedId);
33
+ const relatedResult = await this.chunkLookup(relatedId);
34
34
  if (!relatedResult)
35
35
  continue;
36
36
  const relationship = this.classifyRelationship(nodeId, relatedId);
@@ -1,6 +1,6 @@
1
1
  export type { AnalyzedQuery, QueryIntent, QueryEntity } from './query-analyzer.js';
2
2
  export { QueryAnalyzer } from './query-analyzer.js';
3
- export type { ReadonlyGraph, RelationshipType, RelatedChunk, GraphExcerpt, ExpandedContext, } from './context-expander.js';
3
+ export type { ReadonlyGraph, RelationshipType, RelatedChunk, GraphExcerpt, ExpandedContext, ChunkLookupFn, } from './context-expander.js';
4
4
  export { ContextExpander } from './context-expander.js';
5
5
  export type { TokenBudgetConfig, AssembledContext, } from './token-budget.js';
6
6
  export { TokenBudgetOptimizer } from './token-budget.js';
@@ -0,0 +1,37 @@
1
+ import { type Result } from 'neverthrow';
2
+ import { LanceDBStore } from './embedding/lancedb-store.js';
3
+ import { HybridSearch } from './embedding/hybrid-search.js';
4
+ import { ContextExpander, type ReadonlyGraph } from './retrieval/context-expander.js';
5
+ import type { ReRanker } from './types/provider.js';
6
+ import type { CodeRAGConfig } from './types/config.js';
7
+ /** All services needed at query time, initialized and ready. */
8
+ export interface CodeRAGRuntime {
9
+ readonly config: CodeRAGConfig;
10
+ readonly store: LanceDBStore;
11
+ readonly hybridSearch: HybridSearch;
12
+ readonly contextExpander: ContextExpander | null;
13
+ readonly reranker: ReRanker | null;
14
+ readonly graph: ReadonlyGraph;
15
+ /** Shut down all connections (LanceDB, etc.). */
16
+ close(): void;
17
+ }
18
+ export interface RuntimeOptions {
19
+ /** Project root directory (must contain .coderag.yaml). */
20
+ rootDir: string;
21
+ /**
22
+ * If true, skip context expander, reranker, and graph.
23
+ * Useful for CLI search where only HybridSearch is needed.
24
+ */
25
+ searchOnly?: boolean;
26
+ }
27
+ export declare class RuntimeError extends Error {
28
+ constructor(message: string);
29
+ }
30
+ /**
31
+ * Initialize a CodeRAGRuntime with all services wired together.
32
+ *
33
+ * Loads config, creates embedding provider, connects LanceDB, loads BM25 index,
34
+ * builds HybridSearch, and optionally creates ContextExpander with a real
35
+ * chunkLookup backed by LanceDB, re-ranker, and dependency graph.
36
+ */
37
+ export declare function createRuntime(options: RuntimeOptions): Promise<Result<CodeRAGRuntime, RuntimeError>>;
@@ -0,0 +1,170 @@
1
+ import { ok, err } from 'neverthrow';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { join, resolve } from 'node:path';
4
+ import { loadConfig } from './config/config-parser.js';
5
+ import { OllamaEmbeddingProvider } from './embedding/ollama-embedding-provider.js';
6
+ import { OpenAICompatibleEmbeddingProvider } from './embedding/openai-compatible-embedding-provider.js';
7
+ import { LanceDBStore } from './embedding/lancedb-store.js';
8
+ import { BM25Index } from './embedding/bm25-index.js';
9
+ import { HybridSearch } from './embedding/hybrid-search.js';
10
+ import { DependencyGraph } from './graph/dependency-graph.js';
11
+ import { ContextExpander } from './retrieval/context-expander.js';
12
+ import { CrossEncoderReRanker } from './retrieval/cross-encoder-reranker.js';
13
+ import { safeString, safeStringUnion } from './utils/safe-cast.js';
14
+ export class RuntimeError extends Error {
15
+ constructor(message) {
16
+ super(message);
17
+ this.name = 'RuntimeError';
18
+ }
19
+ }
20
+ /**
21
+ * Build a ChunkLookupFn that resolves chunk IDs via LanceDB.
22
+ * Returns a SearchResult compatible with ContextExpander.
23
+ */
24
+ function buildChunkLookup(store) {
25
+ return async (chunkId) => {
26
+ const result = await store.getById(chunkId);
27
+ if (result.isErr())
28
+ return undefined;
29
+ const row = result.value;
30
+ if (!row)
31
+ return undefined;
32
+ const meta = row.metadata;
33
+ const CHUNK_TYPES = ['function', 'method', 'class', 'module', 'interface', 'type_alias', 'config_block', 'import_block', 'doc'];
34
+ const storedChunkType = safeStringUnion(meta['chunk_type'], CHUNK_TYPES, 'function');
35
+ const storedName = safeString(meta['name'], '');
36
+ const storedFilePath = safeString(meta['file_path'], '');
37
+ const storedLanguage = safeString(meta['language'], 'unknown');
38
+ const storedContent = safeString(meta['content'], '');
39
+ const storedNlSummary = safeString(meta['nl_summary'], '');
40
+ const chunkMetadata = {
41
+ chunkType: storedChunkType,
42
+ name: storedName,
43
+ declarations: [],
44
+ imports: [],
45
+ exports: [],
46
+ };
47
+ return {
48
+ chunkId: row.id,
49
+ content: storedContent,
50
+ nlSummary: storedNlSummary,
51
+ score: 0,
52
+ method: 'hybrid',
53
+ metadata: chunkMetadata,
54
+ chunk: {
55
+ id: row.id,
56
+ content: storedContent,
57
+ nlSummary: storedNlSummary,
58
+ filePath: storedFilePath,
59
+ startLine: 0,
60
+ endLine: 0,
61
+ language: storedLanguage,
62
+ metadata: chunkMetadata,
63
+ },
64
+ };
65
+ };
66
+ }
67
+ /**
68
+ * Create an embedding provider from config.
69
+ * Supports 'ollama' and 'openai-compatible' providers.
70
+ * The 'auto' mode with lifecycle management is only used by the CLI index command.
71
+ */
72
+ function createEmbeddingProvider(config) {
73
+ if (config.embedding.provider === 'openai-compatible' && config.embedding.openaiCompatible) {
74
+ return new OpenAICompatibleEmbeddingProvider({
75
+ baseUrl: config.embedding.openaiCompatible.baseUrl,
76
+ model: config.embedding.model,
77
+ dimensions: config.embedding.dimensions,
78
+ apiKey: config.embedding.openaiCompatible.apiKey,
79
+ maxBatchSize: config.embedding.openaiCompatible.maxBatchSize,
80
+ });
81
+ }
82
+ // Default to Ollama (covers 'ollama', 'auto', and any other value)
83
+ return new OllamaEmbeddingProvider({
84
+ model: config.embedding.model,
85
+ dimensions: config.embedding.dimensions,
86
+ });
87
+ }
88
+ /**
89
+ * Initialize a CodeRAGRuntime with all services wired together.
90
+ *
91
+ * Loads config, creates embedding provider, connects LanceDB, loads BM25 index,
92
+ * builds HybridSearch, and optionally creates ContextExpander with a real
93
+ * chunkLookup backed by LanceDB, re-ranker, and dependency graph.
94
+ */
95
+ export async function createRuntime(options) {
96
+ const { rootDir, searchOnly = false } = options;
97
+ // --- Load config ---
98
+ const configResult = await loadConfig(rootDir);
99
+ if (configResult.isErr()) {
100
+ return err(new RuntimeError(`Config load failed: ${configResult.error.message}`));
101
+ }
102
+ const config = configResult.value;
103
+ // --- Resolve + validate storage path ---
104
+ const storagePath = resolve(rootDir, config.storage.path);
105
+ if (!storagePath.startsWith(resolve(rootDir))) {
106
+ return err(new RuntimeError('Storage path escapes project root'));
107
+ }
108
+ // --- Create embedding provider ---
109
+ const embeddingProvider = createEmbeddingProvider(config);
110
+ // --- Connect LanceDB ---
111
+ const store = new LanceDBStore(storagePath, config.embedding.dimensions);
112
+ try {
113
+ await store.connect();
114
+ }
115
+ catch (error) {
116
+ const message = error instanceof Error ? error.message : 'Unknown error';
117
+ return err(new RuntimeError(`LanceDB connection failed: ${message}`));
118
+ }
119
+ // --- Load BM25 index ---
120
+ let bm25Index = new BM25Index();
121
+ const bm25Path = join(storagePath, 'bm25-index.json');
122
+ try {
123
+ const bm25Data = await readFile(bm25Path, 'utf-8');
124
+ bm25Index = BM25Index.deserialize(bm25Data);
125
+ }
126
+ catch {
127
+ // No saved BM25 index, start empty
128
+ }
129
+ // --- Create HybridSearch ---
130
+ const hybridSearch = new HybridSearch(store, bm25Index, embeddingProvider, config.search);
131
+ // --- Optional: graph, reranker, context expander ---
132
+ let contextExpander = null;
133
+ let reranker = null;
134
+ let graph = new DependencyGraph();
135
+ if (!searchOnly) {
136
+ // Load dependency graph
137
+ const graphPath = join(storagePath, 'graph.json');
138
+ try {
139
+ const graphData = await readFile(graphPath, 'utf-8');
140
+ const parsed = JSON.parse(graphData);
141
+ if (parsed !== null && typeof parsed === 'object' && 'nodes' in parsed && 'edges' in parsed) {
142
+ graph = DependencyGraph.fromJSON(parsed);
143
+ }
144
+ }
145
+ catch {
146
+ // No saved graph, use empty
147
+ }
148
+ // Create re-ranker if enabled
149
+ if (config.reranker?.enabled) {
150
+ reranker = new CrossEncoderReRanker({
151
+ model: config.reranker.model,
152
+ topN: config.reranker.topN,
153
+ });
154
+ }
155
+ // Create context expander with REAL chunk lookup
156
+ const chunkLookup = buildChunkLookup(store);
157
+ contextExpander = new ContextExpander(graph, chunkLookup);
158
+ }
159
+ return ok({
160
+ config,
161
+ store,
162
+ hybridSearch,
163
+ contextExpander,
164
+ reranker,
165
+ graph,
166
+ close() {
167
+ store.close();
168
+ },
169
+ });
170
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Runtime type-checking utilities to replace unsafe `as` type assertions.
3
+ *
4
+ * Each function validates the runtime type of an unknown value and returns
5
+ * a properly typed result, using a fallback when provided or throwing
6
+ * a descriptive TypeError when the value does not match.
7
+ */
8
+ /**
9
+ * Safely extract a string from an unknown value.
10
+ * Returns the value if it is a string, the fallback if provided, or throws.
11
+ */
12
+ export declare function safeString(value: unknown, fallback?: string): string;
13
+ /**
14
+ * Safely extract a number from an unknown value.
15
+ * Returns the value if it is a finite number, the fallback if provided, or throws.
16
+ */
17
+ export declare function safeNumber(value: unknown, fallback?: number): number;
18
+ /**
19
+ * Safely extract a Record<string, unknown> from an unknown value.
20
+ * Returns the value if it is a non-null, non-array object, the fallback if provided, or throws.
21
+ */
22
+ export declare function safeRecord(value: unknown, fallback?: Record<string, unknown>): Record<string, unknown>;
23
+ /**
24
+ * Safely extract an array from an unknown value.
25
+ * Returns the value if it is an array, the fallback if provided, or throws.
26
+ */
27
+ export declare function safeArray(value: unknown, fallback?: unknown[]): unknown[];
28
+ /**
29
+ * Safely extract a string that must be one of the allowed values (union type guard).
30
+ * Returns the value if it matches, the fallback if provided, or throws.
31
+ */
32
+ export declare function safeStringUnion<T extends string>(value: unknown, allowed: readonly T[], fallback?: T): T;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Runtime type-checking utilities to replace unsafe `as` type assertions.
3
+ *
4
+ * Each function validates the runtime type of an unknown value and returns
5
+ * a properly typed result, using a fallback when provided or throwing
6
+ * a descriptive TypeError when the value does not match.
7
+ */
8
+ /**
9
+ * Safely extract a string from an unknown value.
10
+ * Returns the value if it is a string, the fallback if provided, or throws.
11
+ */
12
+ export function safeString(value, fallback) {
13
+ if (typeof value === 'string') {
14
+ return value;
15
+ }
16
+ if (fallback !== undefined) {
17
+ return fallback;
18
+ }
19
+ throw new TypeError(`Expected string, got ${typeof value}`);
20
+ }
21
+ /**
22
+ * Safely extract a number from an unknown value.
23
+ * Returns the value if it is a finite number, the fallback if provided, or throws.
24
+ */
25
+ export function safeNumber(value, fallback) {
26
+ if (typeof value === 'number' && Number.isFinite(value)) {
27
+ return value;
28
+ }
29
+ if (fallback !== undefined) {
30
+ return fallback;
31
+ }
32
+ throw new TypeError(`Expected number, got ${typeof value}`);
33
+ }
34
+ /**
35
+ * Safely extract a Record<string, unknown> from an unknown value.
36
+ * Returns the value if it is a non-null, non-array object, the fallback if provided, or throws.
37
+ */
38
+ export function safeRecord(value, fallback) {
39
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
40
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Runtime guard validates non-null, non-array object
41
+ return value;
42
+ }
43
+ if (fallback !== undefined) {
44
+ return fallback;
45
+ }
46
+ throw new TypeError(`Expected record (object), got ${value === null ? 'null' : Array.isArray(value) ? 'array' : typeof value}`);
47
+ }
48
+ /**
49
+ * Safely extract an array from an unknown value.
50
+ * Returns the value if it is an array, the fallback if provided, or throws.
51
+ */
52
+ export function safeArray(value, fallback) {
53
+ if (Array.isArray(value)) {
54
+ return value;
55
+ }
56
+ if (fallback !== undefined) {
57
+ return fallback;
58
+ }
59
+ throw new TypeError(`Expected array, got ${typeof value}`);
60
+ }
61
+ /**
62
+ * Safely extract a string that must be one of the allowed values (union type guard).
63
+ * Returns the value if it matches, the fallback if provided, or throws.
64
+ */
65
+ export function safeStringUnion(value, allowed, fallback) {
66
+ if (typeof value === 'string') {
67
+ const matched = allowed.find((item) => item === value);
68
+ if (matched !== undefined) {
69
+ return matched;
70
+ }
71
+ }
72
+ if (fallback !== undefined) {
73
+ return fallback;
74
+ }
75
+ throw new TypeError(`Expected one of [${allowed.join(', ')}], got ${typeof value === 'string' ? `"${value}"` : typeof value}`);
76
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-rag/core",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Core library for CodeRAG — intelligent codebase context engine with AST parsing, embeddings, and hybrid search",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,6 +40,10 @@
40
40
  ".": {
41
41
  "import": "./dist/index.js",
42
42
  "types": "./dist/index.d.ts"
43
+ },
44
+ "./api-contracts": {
45
+ "import": "./dist/api-contracts/index.js",
46
+ "types": "./dist/api-contracts/index.d.ts"
43
47
  }
44
48
  },
45
49
  "dependencies": {