@zokizuan/satori-core 1.0.0 → 1.1.0

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,6 +1,6 @@
1
1
  import { Splitter } from '../splitter';
2
2
  import { Embedding } from '../embedding';
3
- import { VectorDatabase } from '../vectordb';
3
+ import { VectorDatabase, IndexCompletionMarkerDocument } from '../vectordb';
4
4
  import { SemanticSearchResult } from '../types';
5
5
  import { FileSynchronizer } from '../sync/synchronizer';
6
6
  export interface ContextConfig {
@@ -126,6 +126,11 @@ export declare class Context {
126
126
  * @param threshold Similarity threshold
127
127
  */
128
128
  semanticSearch(codebasePath: string, query: string, topK?: number, threshold?: number, filterExpr?: string): Promise<SemanticSearchResult[]>;
129
+ private buildSemanticSearchFilterExpr;
130
+ private queryCompletionMarkerRows;
131
+ clearIndexCompletionMarker(codebasePath: string): Promise<void>;
132
+ writeIndexCompletionMarker(codebasePath: string, marker: IndexCompletionMarkerDocument): Promise<void>;
133
+ getIndexCompletionMarker(codebasePath: string): Promise<IndexCompletionMarkerDocument | null>;
129
134
  /**
130
135
  * Check if index exists for codebase
131
136
  * @param codebasePath Codebase path to check
@@ -230,17 +235,12 @@ export declare class Context {
230
235
  */
231
236
  private loadIgnorePatterns;
232
237
  /**
233
- * Find root-level .xxxignore files in the codebase directory.
234
- * v1 policy: only root ignore files are loaded (nested .gitignore files are ignored).
238
+ * Find supported root ignore files in the codebase directory.
239
+ * v1 policy: only repo-root .satoriignore and .gitignore are loaded.
235
240
  * @param codebasePath Path to the codebase
236
241
  * @returns Array of ignore file paths
237
242
  */
238
243
  private findIgnoreFiles;
239
- /**
240
- * Load global ignore file from ~/.satori/.satoriignore
241
- * @returns Array of ignore patterns
242
- */
243
- private loadGlobalIgnoreFile;
244
244
  /**
245
245
  * Load ignore patterns from a specific ignore file
246
246
  * @param filePath Path to the ignore file
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.Context = void 0;
40
40
  const splitter_1 = require("../splitter");
41
41
  const embedding_1 = require("../embedding");
42
+ const vectordb_1 = require("../vectordb");
42
43
  const env_manager_1 = require("../utils/env-manager");
43
44
  const defaults_1 = require("../config/defaults");
44
45
  const language_1 = require("../language");
@@ -283,6 +284,22 @@ class Context {
283
284
  this.synchronizers.set(collectionName, newSynchronizer);
284
285
  }
285
286
  const currentSynchronizer = this.synchronizers.get(collectionName);
287
+ const collectionExists = await this.vectorDatabase.hasCollection(collectionName);
288
+ if (!collectionExists) {
289
+ console.warn(`[Context] ⚠️ Collection '${collectionName}' is missing. Rebuilding full index before incremental sync resumes.`);
290
+ const changedFiles = this.normalizeRelativePathsForCodebase(codebasePath, await this.getCodeFiles(codebasePath));
291
+ if (changedFiles.length === 0) {
292
+ progressCallback?.({ phase: 'No files to index', current: 100, total: 100, percentage: 100 });
293
+ return { added: 0, removed: 0, modified: 0, changedFiles: [] };
294
+ }
295
+ await this.indexCodebase(codebasePath, progressCallback);
296
+ return {
297
+ added: changedFiles.length,
298
+ removed: 0,
299
+ modified: 0,
300
+ changedFiles
301
+ };
302
+ }
286
303
  progressCallback?.({ phase: 'Checking for file changes...', current: 0, total: 100, percentage: 0 });
287
304
  const { added, removed, modified } = await currentSynchronizer.checkForChanges();
288
305
  const totalChanges = added.length + removed.length + modified.length;
@@ -347,6 +364,7 @@ class Context {
347
364
  const isHybrid = this.getIsHybrid();
348
365
  const searchType = isHybrid === true ? 'hybrid search' : 'semantic search';
349
366
  console.log(`[Context] 🔍 Executing ${searchType}: "${query}" in ${codebasePath}`);
367
+ const effectiveFilterExpr = this.buildSemanticSearchFilterExpr(filterExpr);
350
368
  const normalizeBreadcrumbs = (value) => {
351
369
  if (!Array.isArray(value)) {
352
370
  return undefined;
@@ -405,7 +423,8 @@ class Context {
405
423
  params: { k: 100 }
406
424
  },
407
425
  limit: topK,
408
- filterExpr
426
+ threshold,
427
+ filterExpr: effectiveFilterExpr
409
428
  });
410
429
  console.log(`[Context] 🔍 Raw search results count: ${searchResults.length}`);
411
430
  // 4. Convert to semantic search result format
@@ -432,7 +451,7 @@ class Context {
432
451
  // 1. Generate query vector
433
452
  const queryEmbedding = await this.embedding.embed(query);
434
453
  // 2. Search in vector database
435
- const searchResults = await this.vectorDatabase.search(collectionName, queryEmbedding.vector, { topK, threshold, filterExpr });
454
+ const searchResults = await this.vectorDatabase.search(collectionName, queryEmbedding.vector, { topK, threshold, filterExpr: effectiveFilterExpr });
436
455
  // 3. Convert to semantic search result format
437
456
  const results = searchResults.map(result => ({
438
457
  content: result.document.content,
@@ -450,6 +469,103 @@ class Context {
450
469
  return results;
451
470
  }
452
471
  }
472
+ buildSemanticSearchFilterExpr(filterExpr) {
473
+ const markerExclusion = `fileExtension != "${vectordb_1.INDEX_COMPLETION_MARKER_FILE_EXTENSION}"`;
474
+ if (!filterExpr || filterExpr.trim().length === 0) {
475
+ return markerExclusion;
476
+ }
477
+ return `(${filterExpr}) and (${markerExclusion})`;
478
+ }
479
+ async queryCompletionMarkerRows(collectionName) {
480
+ return this.vectorDatabase.query(collectionName, `id == "${vectordb_1.INDEX_COMPLETION_MARKER_DOC_ID}"`, ['id', 'metadata'], 8);
481
+ }
482
+ async clearIndexCompletionMarker(codebasePath) {
483
+ const collectionName = this.resolveCollectionName(codebasePath);
484
+ const hasCollection = await this.vectorDatabase.hasCollection(collectionName);
485
+ if (!hasCollection) {
486
+ return;
487
+ }
488
+ const rows = await this.queryCompletionMarkerRows(collectionName);
489
+ const markerIds = rows
490
+ .map((row) => (typeof row.id === 'string' ? row.id : ''))
491
+ .filter((id) => id.length > 0);
492
+ if (markerIds.length === 0) {
493
+ return;
494
+ }
495
+ await this.vectorDatabase.delete(collectionName, Array.from(new Set(markerIds)));
496
+ }
497
+ async writeIndexCompletionMarker(codebasePath, marker) {
498
+ const collectionName = this.resolveCollectionName(codebasePath);
499
+ const hasCollection = await this.vectorDatabase.hasCollection(collectionName);
500
+ if (!hasCollection) {
501
+ throw new Error(`Cannot write completion marker: collection '${collectionName}' does not exist.`);
502
+ }
503
+ await this.clearIndexCompletionMarker(codebasePath);
504
+ const vector = new Array(this.embedding.getDimension()).fill(0);
505
+ const markerDoc = {
506
+ id: vectordb_1.INDEX_COMPLETION_MARKER_DOC_ID,
507
+ vector,
508
+ content: 'satori index completion marker',
509
+ relativePath: vectordb_1.INDEX_COMPLETION_MARKER_RELATIVE_PATH,
510
+ startLine: 0,
511
+ endLine: 0,
512
+ fileExtension: vectordb_1.INDEX_COMPLETION_MARKER_FILE_EXTENSION,
513
+ metadata: marker,
514
+ };
515
+ if (this.getIsHybrid() === true) {
516
+ await this.vectorDatabase.insertHybrid(collectionName, [markerDoc]);
517
+ }
518
+ else {
519
+ await this.vectorDatabase.insert(collectionName, [markerDoc]);
520
+ }
521
+ }
522
+ async getIndexCompletionMarker(codebasePath) {
523
+ const collectionName = this.resolveCollectionName(codebasePath);
524
+ const hasCollection = await this.vectorDatabase.hasCollection(collectionName);
525
+ if (!hasCollection) {
526
+ return null;
527
+ }
528
+ const rows = await this.queryCompletionMarkerRows(collectionName);
529
+ for (const row of rows) {
530
+ const rawMetadata = row?.metadata;
531
+ if (typeof rawMetadata !== 'string') {
532
+ continue;
533
+ }
534
+ try {
535
+ const parsed = JSON.parse(rawMetadata);
536
+ if (parsed?.kind !== 'satori_index_completion_v1') {
537
+ continue;
538
+ }
539
+ if (typeof parsed.codebasePath !== 'string' || typeof parsed.runId !== 'string') {
540
+ continue;
541
+ }
542
+ if (!parsed.fingerprint || typeof parsed.fingerprint !== 'object') {
543
+ continue;
544
+ }
545
+ const indexedFiles = Number(parsed.indexedFiles);
546
+ const totalChunks = Number(parsed.totalChunks);
547
+ if (!Number.isFinite(indexedFiles) || !Number.isFinite(totalChunks)) {
548
+ continue;
549
+ }
550
+ if (typeof parsed.completedAt !== 'string' || Number.isNaN(Date.parse(parsed.completedAt))) {
551
+ continue;
552
+ }
553
+ return {
554
+ kind: 'satori_index_completion_v1',
555
+ codebasePath: parsed.codebasePath,
556
+ fingerprint: parsed.fingerprint,
557
+ indexedFiles,
558
+ totalChunks,
559
+ completedAt: parsed.completedAt,
560
+ runId: parsed.runId,
561
+ };
562
+ }
563
+ catch {
564
+ continue;
565
+ }
566
+ }
567
+ return null;
568
+ }
453
569
  /**
454
570
  * Check if index exists for codebase
455
571
  * @param codebasePath Codebase path to check
@@ -475,6 +591,8 @@ class Context {
475
591
  }
476
592
  // Delete snapshot file
477
593
  await synchronizer_1.FileSynchronizer.deleteSnapshot(codebasePath);
594
+ this.synchronizers.delete(collectionName);
595
+ this.ignoreStateByCollection.delete(collectionName);
478
596
  progressCallback?.({ phase: 'Index cleared', current: 100, total: 100, percentage: 100 });
479
597
  console.log('[Context] ✅ Index data cleaned');
480
598
  }
@@ -722,6 +840,7 @@ class Context {
722
840
  let processedFiles = 0;
723
841
  let totalChunks = 0;
724
842
  let limitReached = false;
843
+ const describeError = (error) => error instanceof Error ? error.message : String(error);
725
844
  for (let i = 0; i < filePaths.length; i++) {
726
845
  const filePath = filePaths[i];
727
846
  try {
@@ -750,6 +869,7 @@ class Context {
750
869
  if (error instanceof Error) {
751
870
  console.error('[Context] Stack trace:', error.stack);
752
871
  }
872
+ throw new Error(`Failed to persist ${searchType} chunks while indexing ${filePath}: ${describeError(error)}`);
753
873
  }
754
874
  finally {
755
875
  chunkBuffer = []; // Always clear buffer, even on failure
@@ -769,7 +889,8 @@ class Context {
769
889
  }
770
890
  }
771
891
  catch (error) {
772
- console.warn(`[Context] ⚠️ Skipping file ${filePath}: ${error}`);
892
+ console.error(`[Context] Failed to index file ${filePath}: ${describeError(error)}`);
893
+ throw error;
773
894
  }
774
895
  }
775
896
  // Process any remaining chunks in the buffer
@@ -784,6 +905,7 @@ class Context {
784
905
  if (error instanceof Error) {
785
906
  console.error('[Context] Stack trace:', error.stack);
786
907
  }
908
+ throw new Error(`Failed to persist final ${searchType} chunk batch: ${describeError(error)}`);
787
909
  }
788
910
  }
789
911
  return {
@@ -920,18 +1042,15 @@ class Context {
920
1042
  async loadIgnorePatterns(codebasePath) {
921
1043
  try {
922
1044
  let fileBasedPatterns = [];
923
- // Load all .xxxignore files in codebase directory (excluding legacy .contextignore)
1045
+ // v1 policy: only repo-root .satoriignore and .gitignore are supported.
924
1046
  const ignoreFiles = await this.findIgnoreFiles(codebasePath);
925
1047
  for (const ignoreFile of ignoreFiles) {
926
1048
  const patterns = await this.loadIgnoreFile(ignoreFile, path.basename(ignoreFile));
927
1049
  fileBasedPatterns.push(...patterns);
928
1050
  }
929
- // Load global ~/.satori/.satoriignore
930
- const globalIgnorePatterns = await this.loadGlobalIgnoreFile();
931
- fileBasedPatterns.push(...globalIgnorePatterns);
932
1051
  this.setFileBasedPatternsForCodebase(codebasePath, fileBasedPatterns);
933
1052
  if (fileBasedPatterns.length > 0) {
934
- console.log(`[Context] 🚫 Loaded total ${fileBasedPatterns.length} ignore patterns from all ignore files`);
1053
+ console.log(`[Context] 🚫 Loaded total ${fileBasedPatterns.length} ignore patterns from supported root ignore files`);
935
1054
  }
936
1055
  else {
937
1056
  console.log('📄 No ignore files found; effective rules reset to base + runtime custom');
@@ -943,21 +1062,25 @@ class Context {
943
1062
  }
944
1063
  }
945
1064
  /**
946
- * Find root-level .xxxignore files in the codebase directory.
947
- * v1 policy: only root ignore files are loaded (nested .gitignore files are ignored).
1065
+ * Find supported root ignore files in the codebase directory.
1066
+ * v1 policy: only repo-root .satoriignore and .gitignore are loaded.
948
1067
  * @param codebasePath Path to the codebase
949
1068
  * @returns Array of ignore file paths
950
1069
  */
951
1070
  async findIgnoreFiles(codebasePath) {
952
1071
  try {
953
- const entries = await fs.promises.readdir(codebasePath, { withFileTypes: true });
954
1072
  const ignoreFiles = [];
955
- for (const entry of entries) {
956
- if (entry.isFile() &&
957
- entry.name.startsWith('.') &&
958
- entry.name.endsWith('ignore') &&
959
- entry.name !== '.contextignore') {
960
- ignoreFiles.push(path.join(codebasePath, entry.name));
1073
+ const supportedIgnoreFiles = ['.satoriignore', '.gitignore'];
1074
+ for (const fileName of supportedIgnoreFiles) {
1075
+ const absolutePath = path.join(codebasePath, fileName);
1076
+ try {
1077
+ const stat = await fs.promises.stat(absolutePath);
1078
+ if (stat.isFile()) {
1079
+ ignoreFiles.push(absolutePath);
1080
+ }
1081
+ }
1082
+ catch {
1083
+ // Missing ignore file is expected.
961
1084
  }
962
1085
  }
963
1086
  if (ignoreFiles.length > 0) {
@@ -970,21 +1093,6 @@ class Context {
970
1093
  return [];
971
1094
  }
972
1095
  }
973
- /**
974
- * Load global ignore file from ~/.satori/.satoriignore
975
- * @returns Array of ignore patterns
976
- */
977
- async loadGlobalIgnoreFile() {
978
- try {
979
- const homeDir = require('os').homedir();
980
- const globalIgnorePath = path.join(homeDir, '.satori', '.satoriignore');
981
- return await this.loadIgnoreFile(globalIgnorePath, 'global .satoriignore');
982
- }
983
- catch (error) {
984
- // Global ignore file is optional, don't log warnings
985
- return [];
986
- }
987
- }
988
1096
  /**
989
1097
  * Load ignore patterns from a specific ignore file
990
1098
  * @param filePath Path to the ignore file
@@ -1,4 +1,4 @@
1
- export { VectorDocument, SearchOptions, VectorSearchResult, VectorDatabase, CollectionDetails, VectorStoreBackendInfo, HybridSearchRequest, HybridSearchOptions, HybridSearchResult, RerankStrategy, COLLECTION_LIMIT_MESSAGE } from './types';
1
+ export { VectorDocument, SearchOptions, VectorSearchResult, VectorDatabase, CollectionDetails, VectorStoreBackendInfo, HybridSearchRequest, HybridSearchOptions, HybridSearchResult, RerankStrategy, IndexCompletionFingerprint, IndexCompletionMarkerDocument, INDEX_COMPLETION_MARKER_DOC_ID, INDEX_COMPLETION_MARKER_FILE_EXTENSION, INDEX_COMPLETION_MARKER_RELATIVE_PATH, COLLECTION_LIMIT_MESSAGE } from './types';
2
2
  export { MilvusRestfulVectorDatabase, MilvusRestfulConfig } from './milvus-restful-vectordb';
3
3
  export { MilvusVectorDatabase, MilvusConfig } from './milvus-vectordb';
4
4
  export { ClusterManager, ZillizConfig, Project, Cluster, CreateFreeClusterRequest, CreateFreeClusterResponse, CreateFreeClusterWithDetailsResponse, DescribeClusterResponse } from './zilliz-utils';
@@ -1,8 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ClusterManager = exports.MilvusVectorDatabase = exports.MilvusRestfulVectorDatabase = exports.COLLECTION_LIMIT_MESSAGE = void 0;
3
+ exports.ClusterManager = exports.MilvusVectorDatabase = exports.MilvusRestfulVectorDatabase = exports.COLLECTION_LIMIT_MESSAGE = exports.INDEX_COMPLETION_MARKER_RELATIVE_PATH = exports.INDEX_COMPLETION_MARKER_FILE_EXTENSION = exports.INDEX_COMPLETION_MARKER_DOC_ID = void 0;
4
4
  // Re-export types and interfaces
5
5
  var types_1 = require("./types");
6
+ Object.defineProperty(exports, "INDEX_COMPLETION_MARKER_DOC_ID", { enumerable: true, get: function () { return types_1.INDEX_COMPLETION_MARKER_DOC_ID; } });
7
+ Object.defineProperty(exports, "INDEX_COMPLETION_MARKER_FILE_EXTENSION", { enumerable: true, get: function () { return types_1.INDEX_COMPLETION_MARKER_FILE_EXTENSION; } });
8
+ Object.defineProperty(exports, "INDEX_COMPLETION_MARKER_RELATIVE_PATH", { enumerable: true, get: function () { return types_1.INDEX_COMPLETION_MARKER_RELATIVE_PATH; } });
6
9
  Object.defineProperty(exports, "COLLECTION_LIMIT_MESSAGE", { enumerable: true, get: function () { return types_1.COLLECTION_LIMIT_MESSAGE; } });
7
10
  // Implementation class exports
8
11
  var milvus_restful_vectordb_1 = require("./milvus-restful-vectordb");
@@ -69,11 +69,6 @@ export declare class MilvusRestfulVectorDatabase implements VectorDatabase {
69
69
  private createHybridIndexes;
70
70
  insertHybrid(collectionName: string, documents: VectorDocument[]): Promise<void>;
71
71
  hybridSearch(collectionName: string, searchRequests: HybridSearchRequest[], options?: HybridSearchOptions): Promise<HybridSearchResult[]>;
72
- /**
73
- * Check collection limit
74
- * Returns true if collection can be created, false if limit exceeded
75
- * TODO: Implement proper collection limit checking for REST API
76
- */
77
72
  checkCollectionLimit(): Promise<boolean>;
78
73
  }
79
74
  //# sourceMappingURL=milvus-restful-vectordb.d.ts.map
@@ -444,7 +444,7 @@ class MilvusRestfulVectorDatabase {
444
444
  score: item.distance || 0
445
445
  };
446
446
  });
447
- return results;
447
+ return results.filter((result) => options?.threshold === undefined || result.score >= options.threshold);
448
448
  }
449
449
  catch (error) {
450
450
  console.error(`[MilvusRestfulDB] ❌ Failed to search in collection '${collectionName}':`, error);
@@ -720,7 +720,8 @@ class MilvusRestfulVectorDatabase {
720
720
  const results = response.data || [];
721
721
  console.log(`[MilvusRestfulDB] ✅ Found ${results.length} results from hybrid search`);
722
722
  // Transform response to HybridSearchResult format
723
- return results.map((result) => ({
723
+ return results
724
+ .map((result) => ({
724
725
  document: {
725
726
  id: result.id,
726
727
  content: result.content,
@@ -733,23 +734,54 @@ class MilvusRestfulVectorDatabase {
733
734
  metadata: JSON.parse(result.metadata || '{}'),
734
735
  },
735
736
  score: result.score || result.distance || 0,
736
- }));
737
+ }))
738
+ .filter((result) => options?.threshold === undefined || result.score >= options.threshold);
737
739
  }
738
740
  catch (error) {
739
741
  console.error(`[MilvusRestfulDB] ❌ Failed to perform hybrid search on collection '${collectionName}':`, error);
740
742
  throw error;
741
743
  }
742
744
  }
743
- /**
744
- * Check collection limit
745
- * Returns true if collection can be created, false if limit exceeded
746
- * TODO: Implement proper collection limit checking for REST API
747
- */
748
745
  async checkCollectionLimit() {
749
- // TODO: Implement REST API version of collection limit checking
750
- // For now, always return true to maintain compatibility
751
- console.warn('[MilvusRestfulDB] ⚠️ checkCollectionLimit not implemented for REST API - returning true');
752
- return true;
746
+ await this.ensureInitialized();
747
+ const restfulConfig = this.config;
748
+ const collectionName = `dummy_collection_${Date.now()}`;
749
+ const collectionSchema = {
750
+ collectionName,
751
+ dbName: restfulConfig.database,
752
+ schema: {
753
+ enableDynamicField: false,
754
+ fields: [
755
+ {
756
+ fieldName: "id",
757
+ dataType: "VarChar",
758
+ isPrimary: true,
759
+ elementTypeParams: {
760
+ max_length: 512
761
+ }
762
+ },
763
+ {
764
+ fieldName: "vector",
765
+ dataType: "FloatVector",
766
+ elementTypeParams: {
767
+ dim: 128
768
+ }
769
+ }
770
+ ]
771
+ }
772
+ };
773
+ try {
774
+ await createCollectionWithLimitCheck(this.makeRequest.bind(this), collectionSchema);
775
+ await this.dropCollection(collectionName);
776
+ return true;
777
+ }
778
+ catch (error) {
779
+ const message = error instanceof Error ? error.message : String(error);
780
+ if (message === types_1.COLLECTION_LIMIT_MESSAGE) {
781
+ return false;
782
+ }
783
+ throw error;
784
+ }
753
785
  }
754
786
  }
755
787
  exports.MilvusRestfulVectorDatabase = MilvusRestfulVectorDatabase;
@@ -442,7 +442,8 @@ class MilvusVectorDatabase {
442
442
  if (!searchResult.results || searchResult.results.length === 0) {
443
443
  return [];
444
444
  }
445
- return searchResult.results.map((result) => ({
445
+ return searchResult.results
446
+ .map((result) => ({
446
447
  document: {
447
448
  id: result.id,
448
449
  vector: queryVector,
@@ -454,7 +455,8 @@ class MilvusVectorDatabase {
454
455
  metadata: JSON.parse(result.metadata || '{}'),
455
456
  },
456
457
  score: result.score,
457
- }));
458
+ }))
459
+ .filter((result) => options?.threshold === undefined || result.score >= options.threshold);
458
460
  }
459
461
  async delete(collectionName, ids) {
460
462
  await this.ensureInitialized();
@@ -700,7 +702,8 @@ class MilvusVectorDatabase {
700
702
  }
701
703
  console.log(`[MilvusDB] ✅ Found ${searchResult.results.length} results from hybrid search`);
702
704
  // Transform results to HybridSearchResult format
703
- return searchResult.results.map((result) => ({
705
+ return searchResult.results
706
+ .map((result) => ({
704
707
  document: {
705
708
  id: result.id,
706
709
  content: result.content,
@@ -713,7 +716,8 @@ class MilvusVectorDatabase {
713
716
  metadata: JSON.parse(result.metadata || '{}'),
714
717
  },
715
718
  score: result.score,
716
- }));
719
+ }))
720
+ .filter((result) => options?.threshold === undefined || result.score >= options.threshold);
717
721
  }
718
722
  catch (error) {
719
723
  console.error(`[MilvusDB] ❌ Failed to perform hybrid search on collection '${collectionName}':`, error);
@@ -23,6 +23,7 @@ export interface HybridSearchRequest {
23
23
  export interface HybridSearchOptions {
24
24
  rerank?: RerankStrategy;
25
25
  limit?: number;
26
+ threshold?: number;
26
27
  filterExpr?: string;
27
28
  }
28
29
  export interface RerankStrategy {
@@ -46,6 +47,25 @@ export interface VectorStoreBackendInfo {
46
47
  transport: 'grpc' | 'rest';
47
48
  address?: string;
48
49
  }
50
+ export interface IndexCompletionFingerprint {
51
+ embeddingProvider: string;
52
+ embeddingModel: string;
53
+ embeddingDimension: number;
54
+ vectorStoreProvider: string;
55
+ schemaVersion: string;
56
+ }
57
+ export interface IndexCompletionMarkerDocument {
58
+ kind: 'satori_index_completion_v1';
59
+ codebasePath: string;
60
+ fingerprint: IndexCompletionFingerprint;
61
+ indexedFiles: number;
62
+ totalChunks: number;
63
+ completedAt: string;
64
+ runId: string;
65
+ }
66
+ export declare const INDEX_COMPLETION_MARKER_DOC_ID = "__satori_index_completion_marker_v1__";
67
+ export declare const INDEX_COMPLETION_MARKER_FILE_EXTENSION = ".satori_meta";
68
+ export declare const INDEX_COMPLETION_MARKER_RELATIVE_PATH = ".__satori__/index_completion_marker.json";
49
69
  export interface VectorDatabase {
50
70
  /**
51
71
  * Create collection
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.COLLECTION_LIMIT_MESSAGE = void 0;
3
+ exports.COLLECTION_LIMIT_MESSAGE = exports.INDEX_COMPLETION_MARKER_RELATIVE_PATH = exports.INDEX_COMPLETION_MARKER_FILE_EXTENSION = exports.INDEX_COMPLETION_MARKER_DOC_ID = void 0;
4
+ exports.INDEX_COMPLETION_MARKER_DOC_ID = '__satori_index_completion_marker_v1__';
5
+ exports.INDEX_COMPLETION_MARKER_FILE_EXTENSION = '.satori_meta';
6
+ exports.INDEX_COMPLETION_MARKER_RELATIVE_PATH = '.__satori__/index_completion_marker.json';
4
7
  /**
5
8
  * Special error message for collection limit exceeded
6
9
  * This allows us to distinguish it from other errors across all Milvus implementations
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zokizuan/satori-core",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Core semantic indexing engine for Satori's insight-first retrieval",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",