@customize-agent/knowledge 4.0.2 → 4.0.4

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.
Files changed (47) hide show
  1. package/dist/chunking/bge-tokenizer.d.ts +2 -0
  2. package/dist/chunking/bge-tokenizer.js +2 -0
  3. package/dist/chunking/text-chunker.d.ts +10 -0
  4. package/dist/chunking/text-chunker.js +8 -0
  5. package/dist/classification/classifier.d.ts +1 -0
  6. package/dist/classification/classifier.js +1 -0
  7. package/dist/constants.d.ts +7 -0
  8. package/dist/constants.js +7 -0
  9. package/dist/core/change-tracker.d.ts +13 -0
  10. package/dist/core/change-tracker.js +13 -0
  11. package/dist/core/file-scanner.d.ts +13 -0
  12. package/dist/core/file-scanner.js +12 -0
  13. package/dist/core/index-state-store.d.ts +20 -0
  14. package/dist/core/index-state-store.js +20 -0
  15. package/dist/core/knowledge-base-manager.d.ts +4 -1
  16. package/dist/core/knowledge-base-manager.js +12 -4
  17. package/dist/core/multi-project-manager.d.ts +8 -0
  18. package/dist/core/multi-project-manager.js +8 -0
  19. package/dist/core/project-config.d.ts +4 -0
  20. package/dist/core/project-config.js +4 -0
  21. package/dist/core/project-id.d.ts +5 -0
  22. package/dist/core/project-id.js +5 -0
  23. package/dist/core/project-registry.d.ts +1 -0
  24. package/dist/core/project-registry.js +1 -0
  25. package/dist/dedup/dedup-engine.d.ts +3 -0
  26. package/dist/dedup/dedup-engine.js +1 -0
  27. package/dist/dedup/relationship-detector.d.ts +7 -0
  28. package/dist/dedup/relationship-detector.js +7 -0
  29. package/dist/embedding/embedding-provider.d.ts +10 -0
  30. package/dist/embedding/embedding-provider.js +23 -3
  31. package/dist/extraction/content-extractor.d.ts +2 -0
  32. package/dist/extraction/content-extractor.js +10 -9
  33. package/dist/extraction/external-extractor.d.ts +10 -0
  34. package/dist/extraction/external-extractor.js +6 -0
  35. package/dist/extraction/module-resolver.js +2 -2
  36. package/dist/index.js +2 -2
  37. package/dist/search/federation-search.d.ts +7 -0
  38. package/dist/search/federation-search.js +1 -0
  39. package/dist/types.d.ts +10 -0
  40. package/dist/vector/collection-manager.d.ts +3 -0
  41. package/dist/vector/collection-manager.js +3 -0
  42. package/dist/vector/hnsw-vector-store.d.ts +1 -0
  43. package/dist/vector/hnsw-vector-store.js +2 -1
  44. package/dist/vector/types.d.ts +6 -0
  45. package/dist/vector/vector-indexer.d.ts +13 -1
  46. package/dist/vector/vector-indexer.js +23 -6
  47. package/package.json +1 -1
@@ -1,7 +1,9 @@
1
+ /** BGE Tokenizer,使用 tokenizer.json 执行 BGE 模型的真实 Token 化 */
1
2
  export declare class BgeTokenizer {
2
3
  private readonly vocab;
3
4
  private readonly unkToken;
4
5
  constructor(tokenizerPath?: string | undefined);
6
+ /** 统计文本的 Token 数量 */
5
7
  countTokens(text: string): number;
6
8
  encode(text: string): string[];
7
9
  private preTokenize;
@@ -1,6 +1,7 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
+ /** BGE Tokenizer,使用 tokenizer.json 执行 BGE 模型的真实 Token 化 */
4
5
  export class BgeTokenizer {
5
6
  vocab;
6
7
  unkToken;
@@ -13,6 +14,7 @@ export class BgeTokenizer {
13
14
  if (this.vocab.size === 0)
14
15
  throw new Error('BGE tokenizer vocab 为空,无法执行真实 Token 计数');
15
16
  }
17
+ /** 统计文本的 Token 数量 */
16
18
  countTokens(text) {
17
19
  return this.encode(text).length;
18
20
  }
@@ -1,4 +1,5 @@
1
1
  import type { ClassifiedFile } from '../types.js';
2
+ /** 文本切片结果 */
2
3
  export interface TextChunk {
3
4
  index: number;
4
5
  text: string;
@@ -8,13 +9,22 @@ export interface TextChunk {
8
9
  sectionTitle?: string;
9
10
  metadata: Record<string, unknown>;
10
11
  }
12
+ /** 切片配置参数 */
11
13
  export interface ChunkConfig {
12
14
  maxChunkSize: number;
13
15
  overlap: number;
14
16
  headerInjection: boolean;
15
17
  }
18
+ /** 文本切片器,支持文档、表格、代码等多类型文件的递归式切片 */
16
19
  export declare class TextChunker {
17
20
  private readonly tokenizer;
21
+ /**
22
+ * 将文本内容按类型和配置分割为切片
23
+ * @param text 原始文本内容
24
+ * @param file 已分类的文件信息
25
+ * @param metadata 额外元数据
26
+ * @returns 切片列表
27
+ */
18
28
  chunk(text: string, file: ClassifiedFile, metadata?: Record<string, unknown>): TextChunk[];
19
29
  private createCandidates;
20
30
  private createTextCandidates;
@@ -29,8 +29,16 @@ const LANGUAGE_ROUTER = {
29
29
  cpp: { delimiters: /\n(?=(?:class|struct|namespace|template)\s|[\w:*&<>]+\s+\w+\s*\()/u, blockStart: /\{\s*$/u, indentSensitive: false },
30
30
  c: { delimiters: /\n(?=(?:struct|enum)\s|[\w*]+\s+\w+\s*\()/u, blockStart: /\{\s*$/u, indentSensitive: false },
31
31
  };
32
+ /** 文本切片器,支持文档、表格、代码等多类型文件的递归式切片 */
32
33
  export class TextChunker {
33
34
  tokenizer = new BgeTokenizer();
35
+ /**
36
+ * 将文本内容按类型和配置分割为切片
37
+ * @param text 原始文本内容
38
+ * @param file 已分类的文件信息
39
+ * @param metadata 额外元数据
40
+ * @returns 切片列表
41
+ */
34
42
  chunk(text, file, metadata = {}) {
35
43
  const source = text.trim();
36
44
  if (source.length === 0)
@@ -1,5 +1,6 @@
1
1
  import type { Stats } from 'node:fs';
2
2
  import type { ClassifiedFile, FileCategory } from '../types.js';
3
+ /** 文件分类器,根据扩展名将文件归类到预定义的分类体系 */
3
4
  export declare class FileClassifier {
4
5
  private readonly extensionMap;
5
6
  constructor();
@@ -1,5 +1,6 @@
1
1
  import * as path from 'node:path';
2
2
  const DEFAULT_MAX_FILE_SIZE_BYTES = 500 * 1024 * 1024;
3
+ /** 文件分类器,根据扩展名将文件归类到预定义的分类体系 */
3
4
  export class FileClassifier {
4
5
  extensionMap;
5
6
  constructor() {
@@ -1,8 +1,15 @@
1
1
  import type { FileCategory } from './types.js';
2
+ /** 知识库数据目录名 */
2
3
  export declare const KNOWLEDGE_BASE_DIR = "knowledgeBase";
4
+ /** 项目配置文件路径(相对于项目根目录) */
3
5
  export declare const PROJECT_CONFIG_PATH: readonly [".customize-agent", "kb", "project.json"];
6
+ /** 自定义 Agent 用户数据目录 */
4
7
  export declare const USER_DATA_DIR = ".customize-agent";
8
+ /** 全局知识库目录名 */
5
9
  export declare const GLOBAL_KNOWLEDGE_DIR = "global-knowledge";
10
+ /** 所有支持的文件分类 */
6
11
  export declare const ALL_CATEGORIES: readonly FileCategory[];
12
+ /** 各文件分类的默认目录(中文名称) */
7
13
  export declare const DEFAULT_CATEGORY_DIRS: Record<FileCategory, string>;
14
+ /** 各文件分类对应的 Vector Collection 名称(英文,供存储使用) */
8
15
  export declare const COLLECTION_CATEGORY_NAMES: Record<FileCategory, string>;
package/dist/constants.js CHANGED
@@ -1,7 +1,12 @@
1
+ /** 知识库数据目录名 */
1
2
  export const KNOWLEDGE_BASE_DIR = 'knowledgeBase';
3
+ /** 项目配置文件路径(相对于项目根目录) */
2
4
  export const PROJECT_CONFIG_PATH = ['.customize-agent', 'kb', 'project.json'];
5
+ /** 自定义 Agent 用户数据目录 */
3
6
  export const USER_DATA_DIR = '.customize-agent';
7
+ /** 全局知识库目录名 */
4
8
  export const GLOBAL_KNOWLEDGE_DIR = 'global-knowledge';
9
+ /** 所有支持的文件分类 */
5
10
  export const ALL_CATEGORIES = [
6
11
  'document',
7
12
  'spreadsheet',
@@ -14,6 +19,7 @@ export const ALL_CATEGORIES = [
14
19
  'archive',
15
20
  'other',
16
21
  ];
22
+ /** 各文件分类的默认目录(中文名称) */
17
23
  export const DEFAULT_CATEGORY_DIRS = {
18
24
  document: '文档资料',
19
25
  spreadsheet: '表格数据',
@@ -26,6 +32,7 @@ export const DEFAULT_CATEGORY_DIRS = {
26
32
  archive: '压缩包',
27
33
  other: '其他文件',
28
34
  };
35
+ /** 各文件分类对应的 Vector Collection 名称(英文,供存储使用) */
29
36
  export const COLLECTION_CATEGORY_NAMES = {
30
37
  document: 'documents',
31
38
  spreadsheet: 'spreadsheets',
@@ -2,10 +2,23 @@ import type { FileClassifier } from '../classification/classifier.js';
2
2
  import type { DiffResult } from '../types.js';
3
3
  import type { DiskFileStat } from './file-scanner.js';
4
4
  import type { IndexStateStore } from './index-state-store.js';
5
+ /** 文件变更追踪器,用于比对磁盘文件与索引状态之间的差异 */
5
6
  export declare class ChangeTracker {
6
7
  private readonly store;
7
8
  constructor(store: IndexStateStore);
9
+ /**
10
+ * 计算磁盘文件与索引状态之间的差异
11
+ * @param diskFiles 磁盘上的文件列表
12
+ * @param classifier 文件分类器
13
+ * @param kbPath 知识库路径
14
+ * @returns 文件差异对比结果
15
+ */
8
16
  computeDiff(diskFiles: Map<string, DiskFileStat>, classifier: FileClassifier, kbPath: string): Promise<DiffResult>;
17
+ /**
18
+ * 计算文件的 SHA-256 哈希值
19
+ * @param filePath 文件路径
20
+ * @returns SHA-256 哈希字符串
21
+ */
9
22
  hashFile(filePath: string): string;
10
23
  private parseMetadata;
11
24
  }
@@ -1,11 +1,19 @@
1
1
  import * as crypto from 'node:crypto';
2
2
  import * as fs from 'node:fs';
3
3
  import * as path from 'node:path';
4
+ /** 文件变更追踪器,用于比对磁盘文件与索引状态之间的差异 */
4
5
  export class ChangeTracker {
5
6
  store;
6
7
  constructor(store) {
7
8
  this.store = store;
8
9
  }
10
+ /**
11
+ * 计算磁盘文件与索引状态之间的差异
12
+ * @param diskFiles 磁盘上的文件列表
13
+ * @param classifier 文件分类器
14
+ * @param kbPath 知识库路径
15
+ * @returns 文件差异对比结果
16
+ */
9
17
  async computeDiff(diskFiles, classifier, kbPath) {
10
18
  const startTime = Date.now();
11
19
  const indexedFiles = this.store.loadActiveRecords();
@@ -73,6 +81,11 @@ export class ChangeTracker {
73
81
  diffTimeMs: Date.now() - startTime,
74
82
  };
75
83
  }
84
+ /**
85
+ * 计算文件的 SHA-256 哈希值
86
+ * @param filePath 文件路径
87
+ * @returns SHA-256 哈希字符串
88
+ */
76
89
  hashFile(filePath) {
77
90
  return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
78
91
  }
@@ -1,8 +1,21 @@
1
+ /** 磁盘文件信息 */
1
2
  export interface DiskFileStat {
2
3
  size: number;
3
4
  mtime: number;
4
5
  }
6
+ /** 知识库文件扫描器,用于扫描和加载知识库目录中的文件 */
5
7
  export declare class KnowledgeFileScanner {
8
+ /**
9
+ * 扫描知识库目录中的所有文件
10
+ * @param kbPath 知识库路径
11
+ * @param ignorePatterns 忽略模式列表
12
+ * @returns 文件相对路径到文件信息的映射
13
+ */
6
14
  scan(kbPath: string, ignorePatterns?: string[]): Promise<Map<string, DiskFileStat>>;
15
+ /**
16
+ * 加载 .kbignore 忽略规则文件
17
+ * @param kbPath 知识库路径
18
+ * @returns 忽略规则列表
19
+ */
7
20
  loadKbIgnore(kbPath: string): string[];
8
21
  }
@@ -1,7 +1,14 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import fg from 'fast-glob';
4
+ /** 知识库文件扫描器,用于扫描和加载知识库目录中的文件 */
4
5
  export class KnowledgeFileScanner {
6
+ /**
7
+ * 扫描知识库目录中的所有文件
8
+ * @param kbPath 知识库路径
9
+ * @param ignorePatterns 忽略模式列表
10
+ * @returns 文件相对路径到文件信息的映射
11
+ */
5
12
  async scan(kbPath, ignorePatterns = []) {
6
13
  if (!fs.existsSync(kbPath))
7
14
  return new Map();
@@ -20,6 +27,11 @@ export class KnowledgeFileScanner {
20
27
  }
21
28
  return files;
22
29
  }
30
+ /**
31
+ * 加载 .kbignore 忽略规则文件
32
+ * @param kbPath 知识库路径
33
+ * @returns 忽略规则列表
34
+ */
23
35
  loadKbIgnore(kbPath) {
24
36
  const ignorePath = path.join(kbPath, '.kbignore');
25
37
  if (!fs.existsSync(ignorePath))
@@ -84,12 +84,16 @@ export interface FileRelationship {
84
84
  userConfirmed: number;
85
85
  createdAt: number;
86
86
  }
87
+ /** 索引状态存储器,使用 SQLite 管理知识库索引的持久化状态 */
87
88
  export declare class IndexStateStore {
88
89
  private readonly db;
89
90
  private ftsEnabled;
90
91
  constructor(dbPath: string);
92
+ /** 加载所有活跃的索引记录 */
91
93
  loadActiveRecords(): Map<string, IndexStateRecord>;
94
+ /** 插入或更新索引记录 */
92
95
  upsertRecord(record: IndexStateRecord): void;
96
+ /** 更新已验证文件的时间戳和状态 */
93
97
  updateVerified(relativePath: string, mtime: number): void;
94
98
  listRecords(): IndexStateRecord[];
95
99
  enqueueIndexJob(job: {
@@ -108,6 +112,12 @@ export declare class IndexStateStore {
108
112
  countPendingIndexJobs(): number;
109
113
  listActiveIndexJobsByPath(relativePath: string): KnowledgeIndexJob[];
110
114
  listIndexJobsByPrefix(prefix: string): KnowledgeIndexJob[];
115
+ /**
116
+ * 替换指定文件的切片数据(使用事务批量更新)
117
+ * @param relativePath 文件相对路径
118
+ * @param chunks 文本切片列表
119
+ * @param file 文件分类信息
120
+ */
111
121
  replaceChunks(relativePath: string, chunks: TextChunk[], file: {
112
122
  category: FileCategory;
113
123
  format: string;
@@ -125,6 +135,12 @@ export declare class IndexStateStore {
125
135
  getParentChunk(relativePath: string, parentId: string): StoredParentChunk | undefined;
126
136
  getDocumentChunk(relativePath: string): StoredDocumentChunk | undefined;
127
137
  getChunksByParent(relativePath: string, parentId: string, limit?: number): StoredChunk[];
138
+ /**
139
+ * 使用关键词搜索切片(支持 FTS5 全文搜索和 LIKE 模糊匹配)
140
+ * @param query 搜索查询
141
+ * @param limit 返回结果数量上限
142
+ * @returns 搜索结果列表(按相关性得分排序)
143
+ */
128
144
  searchChunks(query: string, limit?: number): ChunkSearchResult[];
129
145
  private searchChunksFts;
130
146
  private searchChunksLike;
@@ -149,6 +165,10 @@ export declare class IndexStateStore {
149
165
  enabled: boolean;
150
166
  createdAt: number;
151
167
  }>;
168
+ /**
169
+ * 删除指定文件的全部索引数据(含切片、哈希、MinHash、标签、关系等)
170
+ * @param relativePath 文件相对路径
171
+ */
152
172
  deleteRecord(relativePath: string): void;
153
173
  setMetadata(key: string, value: string): void;
154
174
  getMetadata(key: string): string | undefined;
@@ -1,6 +1,7 @@
1
1
  import Database from 'better-sqlite3';
2
2
  import * as fs from 'node:fs';
3
3
  import * as path from 'node:path';
4
+ /** 索引状态存储器,使用 SQLite 管理知识库索引的持久化状态 */
4
5
  export class IndexStateStore {
5
6
  db;
6
7
  ftsEnabled = false;
@@ -10,6 +11,7 @@ export class IndexStateStore {
10
11
  this.db.pragma('journal_mode = WAL');
11
12
  this.initTables();
12
13
  }
14
+ /** 加载所有活跃的索引记录 */
13
15
  loadActiveRecords() {
14
16
  const rows = this.db.prepare(`
15
17
  SELECT * FROM kb_index_state
@@ -20,6 +22,7 @@ export class IndexStateStore {
20
22
  return [record.relativePath, record];
21
23
  }));
22
24
  }
25
+ /** 插入或更新索引记录 */
23
26
  upsertRecord(record) {
24
27
  this.db.prepare(`
25
28
  INSERT INTO kb_index_state (
@@ -41,6 +44,7 @@ export class IndexStateStore {
41
44
  metadata_json = excluded.metadata_json
42
45
  `).run(record.relativePath, record.category, record.format, record.contentHash, record.fileSize, Math.round(record.mtime), record.chunkCount, record.collectionName, record.indexedAt, record.lastVerifiedAt, record.status, record.errorMessage ?? null, record.metadataJson ?? null);
43
46
  }
47
+ /** 更新已验证文件的时间戳和状态 */
44
48
  updateVerified(relativePath, mtime) {
45
49
  this.db.prepare(`
46
50
  UPDATE kb_index_state
@@ -108,6 +112,12 @@ export class IndexStateStore {
108
112
  `).all(`${prefix}%`);
109
113
  return rows.map(row => this.rowToJob(row));
110
114
  }
115
+ /**
116
+ * 替换指定文件的切片数据(使用事务批量更新)
117
+ * @param relativePath 文件相对路径
118
+ * @param chunks 文本切片列表
119
+ * @param file 文件分类信息
120
+ */
111
121
  replaceChunks(relativePath, chunks, file) {
112
122
  const now = Date.now();
113
123
  const transaction = this.db.transaction(() => {
@@ -237,6 +247,12 @@ export class IndexStateStore {
237
247
  `).all(relativePath, `%"parentId":"${parentId.replace(/[%_]/gu, '')}"%`, limit);
238
248
  return rows.map(row => this.rowToChunk(row, 0));
239
249
  }
250
+ /**
251
+ * 使用关键词搜索切片(支持 FTS5 全文搜索和 LIKE 模糊匹配)
252
+ * @param query 搜索查询
253
+ * @param limit 返回结果数量上限
254
+ * @returns 搜索结果列表(按相关性得分排序)
255
+ */
240
256
  searchChunks(query, limit = 10) {
241
257
  const terms = this.expandSearchTerms(query);
242
258
  if (terms.length === 0)
@@ -410,6 +426,10 @@ export class IndexStateStore {
410
426
  createdAt: Number(row.created_at),
411
427
  }));
412
428
  }
429
+ /**
430
+ * 删除指定文件的全部索引数据(含切片、哈希、MinHash、标签、关系等)
431
+ * @param relativePath 文件相对路径
432
+ */
413
433
  deleteRecord(relativePath) {
414
434
  this.db.prepare('DELETE FROM kb_chunks WHERE relative_path = ?').run(relativePath);
415
435
  this.db.prepare('DELETE FROM kb_parent_chunks WHERE relative_path = ?').run(relativePath);
@@ -94,7 +94,10 @@ export declare class KnowledgeBaseManager {
94
94
  createdAt: number;
95
95
  }[];
96
96
  } | undefined;
97
- reindexFile(relativePath: string): Promise<DiffResult>;
97
+ reindexFile(relativePath: string, options?: {
98
+ onProgress?: (progress: KnowledgeIndexProgress) => void;
99
+ vectorMode?: 'sync' | 'defer';
100
+ }): Promise<DiffResult>;
98
101
  addFile(sourcePath: string, targetRelativePath?: string): Promise<DiffResult>;
99
102
  getUploadRelativePath(fileName: string, targetRelativePath?: string): string;
100
103
  uploadFile(fileName: string, content: Buffer, targetRelativePath?: string, onProgress?: (progress: KnowledgeIndexProgress) => void, options?: {
@@ -331,7 +331,7 @@ export class KnowledgeBaseManager {
331
331
  try {
332
332
  rankedLists.push({ source: 'vector', items: (await this.semanticSearch(rewritten, { ...options, limit: limit * 3 })).results, queryIndex });
333
333
  }
334
- catch { /* vector search is optional in hybrid search */ }
334
+ catch { /* 向量搜索在混合搜索中是可选的 */ }
335
335
  }
336
336
  }
337
337
  const keywordItems = rankedLists.filter(list => list.source === 'keyword').flatMap(list => list.items);
@@ -396,7 +396,7 @@ export class KnowledgeBaseManager {
396
396
  tags: this.store.listTags(normalized),
397
397
  };
398
398
  }
399
- async reindexFile(relativePath) {
399
+ async reindexFile(relativePath, options = {}) {
400
400
  const normalized = this.normalizeRelativePath(relativePath);
401
401
  const record = this.store.listRecords().find(item => item.relativePath === normalized);
402
402
  const targetPath = this.resolveKbRelativePath(normalized);
@@ -405,7 +405,7 @@ export class KnowledgeBaseManager {
405
405
  if (record)
406
406
  await this.deleteVectorFile(record.collectionName, normalized);
407
407
  this.store.deleteRecord(normalized);
408
- return this.incrementalIndex();
408
+ return this.incrementalIndex({ ...options, onlyRelativePaths: [normalized] });
409
409
  }
410
410
  async addFile(sourcePath, targetRelativePath) {
411
411
  this.initialize();
@@ -497,7 +497,15 @@ export class KnowledgeBaseManager {
497
497
  this.reportProgress({ stage: 'vectorizing', percent: 85, message: `正在写入 HNSWLib 向量库,共 ${chunks.length} 个切片`, chunkCount: chunks.length });
498
498
  const indexer = new VectorIndexer(this.embeddingProvider, this.vectorStores);
499
499
  try {
500
- const results = await indexer.indexChunks(chunks);
500
+ const results = await indexer.indexChunks(chunks, {
501
+ onProgress: progress => {
502
+ const percent = 85 + Math.round((progress.processedChunks / Math.max(1, progress.totalChunks)) * 14);
503
+ const message = `正在分批向量化并写入:${progress.processedChunks}/${progress.totalChunks} 个切片`;
504
+ this.reportProgress({ stage: 'vectorizing', percent, message, chunkCount: progress.totalChunks });
505
+ if (options.relativePath)
506
+ this.updateJobsForFile(options.relativePath, 'INDEXING', percent, message);
507
+ },
508
+ });
501
509
  const actualModel = results[0]?.embeddingModel ?? this.embeddingProvider.model;
502
510
  const actualDimension = results[0]?.embeddingDimension ?? this.embeddingProvider.dimensions;
503
511
  this.store.setMetadata('embedding_model', actualModel);
@@ -2,6 +2,7 @@ import type { FederatedResult, RetrievalWeights, SearchFilters, SearchScope } fr
2
2
  import type { CrossProjectDuplicate, ProjectInfo } from '../types.js';
3
3
  import { KnowledgeBaseManager } from './knowledge-base-manager.js';
4
4
  import type { LLMSearchProvider } from '../llm/llm-search-provider.js';
5
+ /** 多项目管理器,管理多个项目的知识库并支持跨项目搜索 */
5
6
  export declare class MultiProjectManager {
6
7
  private readonly storageRoot;
7
8
  private readonly llmProvider?;
@@ -11,7 +12,14 @@ export declare class MultiProjectManager {
11
12
  private readonly lastSearchIndexCheck;
12
13
  private globalKB?;
13
14
  constructor(storageRoot?: string, llmProvider?: LLMSearchProvider);
15
+ /**
16
+ * 获取或创建指定项目的知识库管理器
17
+ * @param projectRoot 项目根目录
18
+ */
14
19
  getProject(projectRoot: string): Promise<KnowledgeBaseManager>;
20
+ /**
21
+ * 获取或初始化全局知识库
22
+ */
15
23
  getGlobalKB(): Promise<KnowledgeBaseManager>;
16
24
  listProjects(): Promise<ProjectInfo[]>;
17
25
  search(projectRoot: string, query: string, options?: {
@@ -6,6 +6,7 @@ import { KnowledgeBaseManager } from './knowledge-base-manager.js';
6
6
  import { computeProjectId } from './project-id.js';
7
7
  import { getProjectKbPath, ProjectConfigManager } from './project-config.js';
8
8
  import { ProjectRegistry } from './project-registry.js';
9
+ /** 多项目管理器,管理多个项目的知识库并支持跨项目搜索 */
9
10
  export class MultiProjectManager {
10
11
  storageRoot;
11
12
  llmProvider;
@@ -20,6 +21,10 @@ export class MultiProjectManager {
20
21
  this.registry = new ProjectRegistry(path.join(storageRoot, 'projects', 'registry.db'));
21
22
  this.configManager = new ProjectConfigManager(storageRoot);
22
23
  }
24
+ /**
25
+ * 获取或创建指定项目的知识库管理器
26
+ * @param projectRoot 项目根目录
27
+ */
23
28
  async getProject(projectRoot) {
24
29
  const resolvedRoot = path.resolve(projectRoot);
25
30
  const projectId = computeProjectId(resolvedRoot);
@@ -40,6 +45,9 @@ export class MultiProjectManager {
40
45
  this.updateRegistry(manager, resolvedRoot, config.projectName, config.lastOpenedAt);
41
46
  return manager;
42
47
  }
48
+ /**
49
+ * 获取或初始化全局知识库
50
+ */
43
51
  async getGlobalKB() {
44
52
  if (this.globalKB)
45
53
  return this.globalKB;
@@ -1,7 +1,11 @@
1
1
  import type { ProjectConfig } from '../types.js';
2
+ /** 获取项目配置文件路径 */
2
3
  export declare function getProjectConfigPath(projectRoot: string, storageRoot?: string): string;
4
+ /** 获取项目知识库目录路径 */
3
5
  export declare function getProjectKbPath(projectRoot: string): string;
6
+ /** 确保项目存在 CUSTOMIZE.md 文件(如不存在则创建默认模板) */
4
7
  export declare function ensureProjectCustomizeFile(projectRoot: string): void;
8
+ /** 项目配置管理器,负责加载和保存项目配置 */
5
9
  export declare class ProjectConfigManager {
6
10
  private readonly storageRoot;
7
11
  constructor(storageRoot?: string);
@@ -3,10 +3,12 @@ import * as path from 'node:path';
3
3
  import * as os from 'node:os';
4
4
  import { DEFAULT_CATEGORY_DIRS, KNOWLEDGE_BASE_DIR, USER_DATA_DIR } from '../constants.js';
5
5
  import { computeProjectId } from './project-id.js';
6
+ /** 获取项目配置文件路径 */
6
7
  export function getProjectConfigPath(projectRoot, storageRoot = path.join(os.homedir(), USER_DATA_DIR)) {
7
8
  const projectId = computeProjectId(projectRoot);
8
9
  return path.join(storageRoot, 'projects', projectId, 'project.json');
9
10
  }
11
+ /** 获取项目知识库目录路径 */
10
12
  export function getProjectKbPath(projectRoot) {
11
13
  return path.join(projectRoot, KNOWLEDGE_BASE_DIR);
12
14
  }
@@ -27,6 +29,7 @@ const DEFAULT_CUSTOMIZE_MD = `# Customize Agent 配置示例
27
29
  - 使用中文回复。
28
30
  - 重要改动完成后运行必要的类型检查或构建检查。
29
31
  `;
32
+ /** 确保项目存在 CUSTOMIZE.md 文件(如不存在则创建默认模板) */
30
33
  export function ensureProjectCustomizeFile(projectRoot) {
31
34
  const filePath = path.join(projectRoot, 'CUSTOMIZE.md');
32
35
  if (!fs.existsSync(filePath)) {
@@ -34,6 +37,7 @@ export function ensureProjectCustomizeFile(projectRoot) {
34
37
  fs.writeFileSync(filePath, DEFAULT_CUSTOMIZE_MD, 'utf8');
35
38
  }
36
39
  }
40
+ /** 项目配置管理器,负责加载和保存项目配置 */
37
41
  export class ProjectConfigManager {
38
42
  storageRoot;
39
43
  constructor(storageRoot = path.join(os.homedir(), USER_DATA_DIR)) {
@@ -1 +1,6 @@
1
+ /**
2
+ * 使用项目根目录计算项目唯一标识
3
+ * @param projectRoot 项目根目录路径
4
+ * @returns 项目 ID(SHA-256 前 12 位)
5
+ */
1
6
  export declare function computeProjectId(projectRoot: string): string;
@@ -1,5 +1,10 @@
1
1
  import * as crypto from 'node:crypto';
2
2
  import * as path from 'node:path';
3
+ /**
4
+ * 使用项目根目录计算项目唯一标识
5
+ * @param projectRoot 项目根目录路径
6
+ * @returns 项目 ID(SHA-256 前 12 位)
7
+ */
3
8
  export function computeProjectId(projectRoot) {
4
9
  return crypto.createHash('sha256')
5
10
  .update(path.resolve(projectRoot))
@@ -1,4 +1,5 @@
1
1
  import type { ProjectInfo } from '../types.js';
2
+ /** 项目注册表,使用 SQLite 管理已注册项目的元数据 */
2
3
  export declare class ProjectRegistry {
3
4
  private readonly db;
4
5
  constructor(dbPath: string);
@@ -1,6 +1,7 @@
1
1
  import Database from 'better-sqlite3';
2
2
  import * as fs from 'node:fs';
3
3
  import * as path from 'node:path';
4
+ /** 项目注册表,使用 SQLite 管理已注册项目的元数据 */
4
5
  export class ProjectRegistry {
5
6
  db;
6
7
  constructor(dbPath) {
@@ -1,12 +1,15 @@
1
+ /** MinHash 签名结果 */
1
2
  export interface MinHashSignature {
2
3
  signature: number[];
3
4
  shingleCount: number;
4
5
  buckets: string[];
5
6
  }
7
+ /** 相似度匹配结果 */
6
8
  export interface SimilarityMatch {
7
9
  filePath: string;
8
10
  similarity: number;
9
11
  }
12
+ /** 去重引擎,支持归一化哈希、MinHash 相似度计算和 LSH 分桶 */
10
13
  export declare class DedupEngine {
11
14
  private readonly hashCount;
12
15
  private readonly bandSize;
@@ -1,4 +1,5 @@
1
1
  import * as crypto from 'node:crypto';
2
+ /** 去重引擎,支持归一化哈希、MinHash 相似度计算和 LSH 分桶 */
2
3
  export class DedupEngine {
3
4
  hashCount = 192;
4
5
  bandSize = 6;
@@ -1,6 +1,13 @@
1
1
  import type { ClassifiedFile, IndexStateRecord } from '../types.js';
2
2
  import type { FileRelationship } from '../core/index-state-store.js';
3
+ /** 文件关系检测器,自动识别版本链、翻译关系和互补关系 */
3
4
  export declare class RelationshipDetector {
5
+ /**
6
+ * 检测文件与已有索引记录之间的关系
7
+ * @param file 当前处理的文件
8
+ * @param indexedRecords 已有索引记录列表
9
+ * @returns 检测到的关系列表
10
+ */
4
11
  detect(file: ClassifiedFile, indexedRecords: IndexStateRecord[]): Array<Omit<FileRelationship, 'id' | 'createdAt'>>;
5
12
  private detectVersionChain;
6
13
  private detectTranslation;
@@ -1,5 +1,12 @@
1
1
  import * as path from 'node:path';
2
+ /** 文件关系检测器,自动识别版本链、翻译关系和互补关系 */
2
3
  export class RelationshipDetector {
4
+ /**
5
+ * 检测文件与已有索引记录之间的关系
6
+ * @param file 当前处理的文件
7
+ * @param indexedRecords 已有索引记录列表
8
+ * @returns 检测到的关系列表
9
+ */
3
10
  detect(file, indexedRecords) {
4
11
  const relationships = [];
5
12
  for (const record of indexedRecords) {
@@ -1,9 +1,11 @@
1
+ /** Embedding Provider 接口,负责将文本转换为向量 */
1
2
  export interface EmbeddingProvider {
2
3
  readonly model: string;
3
4
  readonly dimensions: number;
4
5
  embedDocuments(texts: string[]): Promise<number[][]>;
5
6
  embedQuery(text: string): Promise<number[]>;
6
7
  }
8
+ /** 基于哈希的本地 Embedding Provider(无需模型,使用哈希算法生成特征向量) */
7
9
  export declare class HashEmbeddingProvider implements EmbeddingProvider {
8
10
  readonly dimensions: number;
9
11
  readonly model = "hash-embedding-local";
@@ -20,6 +22,7 @@ export interface OpenAICompatibleEmbeddingOptions {
20
22
  model: string;
21
23
  dimensions?: number;
22
24
  }
25
+ /** OpenAI 兼容的 Embedding Provider,支持任何兼容 OpenAI API 的嵌入服务 */
23
26
  export declare class OpenAICompatibleEmbeddingProvider implements EmbeddingProvider {
24
27
  readonly model: string;
25
28
  readonly dimensions: number;
@@ -34,11 +37,14 @@ export interface LocalTransformersEmbeddingOptions {
34
37
  model?: string;
35
38
  dimensions?: number;
36
39
  modelPath?: string;
40
+ batchSize?: number;
37
41
  }
42
+ /** 本地 Transformers.js 模型 Embedding Provider,使用 BGE 小模型生成向量 */
38
43
  export declare class LocalTransformersEmbeddingProvider implements EmbeddingProvider {
39
44
  readonly model: string;
40
45
  readonly dimensions: number;
41
46
  private readonly modelPath?;
47
+ private readonly batchSize;
42
48
  private static pipelines;
43
49
  constructor(options?: LocalTransformersEmbeddingOptions);
44
50
  embedDocuments(texts: string[]): Promise<number[][]>;
@@ -52,4 +58,8 @@ export declare class LocalTransformersEmbeddingProvider implements EmbeddingProv
52
58
  private resizeVector;
53
59
  private normalize;
54
60
  }
61
+ /**
62
+ * 根据环境变量或配置文件自动创建 Embedding Provider
63
+ * 优先级:环境变量 > 配置文件 > 本地 Transformers.js 默认
64
+ */
55
65
  export declare function createEmbeddingProviderFromEnvironment(): EmbeddingProvider;
@@ -3,6 +3,7 @@ import * as fs from 'node:fs';
3
3
  import * as os from 'node:os';
4
4
  import * as path from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
+ /** 基于哈希的本地 Embedding Provider(无需模型,使用哈希算法生成特征向量) */
6
7
  export class HashEmbeddingProvider {
7
8
  dimensions;
8
9
  model = 'hash-embedding-local';
@@ -48,6 +49,7 @@ export class HashEmbeddingProvider {
48
49
  return vector.map(value => value / norm);
49
50
  }
50
51
  }
52
+ /** OpenAI 兼容的 Embedding Provider,支持任何兼容 OpenAI API 的嵌入服务 */
51
53
  export class OpenAICompatibleEmbeddingProvider {
52
54
  model;
53
55
  dimensions;
@@ -80,6 +82,13 @@ export class OpenAICompatibleEmbeddingProvider {
80
82
  return (payload.data ?? []).map(item => item.embedding ?? []);
81
83
  }
82
84
  }
85
+ function resolveLocalEmbeddingBatchSize(configured) {
86
+ const raw = configured ?? Number(process.env.CUSTOMIZE_EMBEDDING_BATCH_SIZE ?? process.env.KB_EMBEDDING_BATCH_SIZE);
87
+ const fallback = process.platform === 'win32' ? 8 : 16;
88
+ if (!Number.isFinite(raw) || raw <= 0)
89
+ return fallback;
90
+ return Math.max(1, Math.min(128, Math.floor(raw)));
91
+ }
83
92
  function resolveBundledBgeModelPath() {
84
93
  const currentDir = path.dirname(fileURLToPath(import.meta.url));
85
94
  const candidates = [
@@ -91,15 +100,18 @@ function resolveBundledBgeModelPath() {
91
100
  ].filter(Boolean);
92
101
  return candidates.find(candidate => fs.existsSync(path.join(candidate, 'config.json')) && fs.existsSync(path.join(candidate, 'tokenizer.json')));
93
102
  }
103
+ /** 本地 Transformers.js 模型 Embedding Provider,使用 BGE 小模型生成向量 */
94
104
  export class LocalTransformersEmbeddingProvider {
95
105
  model;
96
106
  dimensions;
97
107
  modelPath;
108
+ batchSize;
98
109
  static pipelines = new Map();
99
110
  constructor(options = {}) {
100
111
  this.model = options.model?.trim() || 'BAAI/bge-small-zh-v1.5';
101
112
  this.dimensions = options.dimensions ?? 512;
102
113
  this.modelPath = options.modelPath || resolveBundledBgeModelPath();
114
+ this.batchSize = resolveLocalEmbeddingBatchSize(options.batchSize);
103
115
  }
104
116
  async embedDocuments(texts) {
105
117
  return this.embed(texts);
@@ -109,9 +121,13 @@ export class LocalTransformersEmbeddingProvider {
109
121
  }
110
122
  async embed(input) {
111
123
  const extractor = await this.getPipeline();
112
- const output = await extractor(input, { pooling: 'mean', normalize: true });
113
- const vectors = this.parseVectors(output, input.length);
114
- return vectors.map(vector => this.resizeVector(vector));
124
+ const vectors = [];
125
+ for (let offset = 0; offset < input.length; offset += this.batchSize) {
126
+ const batch = input.slice(offset, offset + this.batchSize);
127
+ const output = await extractor(batch, { pooling: 'mean', normalize: true });
128
+ vectors.push(...this.parseVectors(output, batch.length).map(vector => this.resizeVector(vector)));
129
+ }
130
+ return vectors;
115
131
  }
116
132
  getPipeline() {
117
133
  const existing = LocalTransformersEmbeddingProvider.pipelines.get(this.model);
@@ -200,6 +216,10 @@ function readStoredEmbeddingConfig() {
200
216
  return undefined;
201
217
  }
202
218
  }
219
+ /**
220
+ * 根据环境变量或配置文件自动创建 Embedding Provider
221
+ * 优先级:环境变量 > 配置文件 > 本地 Transformers.js 默认
222
+ */
203
223
  export function createEmbeddingProviderFromEnvironment() {
204
224
  const stored = readStoredEmbeddingConfig();
205
225
  const provider = process.env.CUSTOMIZE_EMBEDDING_PROVIDER ?? process.env.KB_EMBEDDING_PROVIDER ?? stored?.provider ?? 'transformers-local';
@@ -1,11 +1,13 @@
1
1
  import { ExternalExtractorRegistry } from './external-extractor.js';
2
2
  import type { ClassifiedFile } from '../types.js';
3
+ /** 文件内容提取结果 */
3
4
  export interface ExtractionResult {
4
5
  text: string;
5
6
  metadata: Record<string, unknown>;
6
7
  warnings: string[];
7
8
  extractionTimeMs: number;
8
9
  }
10
+ /** 文件内容提取器,支持文档、表格、图片、CAD 等多种文件格式的内容抽取 */
9
11
  export declare class ContentExtractor {
10
12
  private readonly externalExtractors;
11
13
  constructor(externalExtractors?: ExternalExtractorRegistry);
@@ -5,6 +5,7 @@ import { tmpdir } from 'node:os';
5
5
  import { pathToFileURL } from 'node:url';
6
6
  import { ExternalExtractorRegistry } from './external-extractor.js';
7
7
  import { resolveAndImport, resolvePackage, getNodeModulesRoot } from './module-resolver.js';
8
+ /** 文件内容提取器,支持文档、表格、图片、CAD 等多种文件格式的内容抽取 */
8
9
  export class ContentExtractor {
9
10
  externalExtractors;
10
11
  constructor(externalExtractors = ExternalExtractorRegistry.fromEnvironment()) {
@@ -694,7 +695,7 @@ export class ContentExtractor {
694
695
  }
695
696
  }
696
697
  catch {
697
- // fallback below
698
+ // 降级到下方 Office ZIP 解析
698
699
  }
699
700
  }
700
701
  return this.extractOfficeZip(file);
@@ -805,7 +806,7 @@ export class ContentExtractor {
805
806
  catch {
806
807
  if (ext === '.xls')
807
808
  return this.extractLegacyOfficeBinary(file);
808
- // fallback below
809
+ // 降级到下方 ZIP 文件内容提取
809
810
  }
810
811
  if (ext === '.xls')
811
812
  return this.extractLegacyOfficeBinary(file);
@@ -1026,7 +1027,7 @@ try {
1026
1027
  async extractPdf(file) {
1027
1028
  const metadata = { extractionMode: 'pdf_text', vectorizable: true };
1028
1029
  const warnings = [];
1029
- // Tier 1: pdfjs-dist 文本提取(处理常规 PDF、压缩内容流、CJK 字体等)
1030
+ // 第一层:pdfjs-dist 文本提取(处理常规 PDF、压缩内容流、CJK 字体等)
1030
1031
  try {
1031
1032
  const raw = fs.readFileSync(file.absolutePath);
1032
1033
  const text = await this.extractPdfText(raw);
@@ -1040,11 +1041,11 @@ try {
1040
1041
  warnings.push(`PDF 文本提取失败: ${error instanceof Error ? error.message : String(error)}`);
1041
1042
  metadata.parseError = error instanceof Error ? error.message : String(error);
1042
1043
  }
1043
- // Tier 2: OCR(扫描件/图片型 PDF)—— 必须保留并确保可用
1044
+ // 第二层:OCR(扫描件/图片型 PDF)—— 必须保留并确保可用
1044
1045
  const ocr = await this.extractScannedPdfOcr(file);
1045
1046
  if (ocr.text.trim())
1046
1047
  return ocr;
1047
- // Tier 3: 仅索引元数据(兜底)
1048
+ // 第三层:仅索引元数据(兜底)
1048
1049
  metadata.extractionMode = 'pdf_metadata_only';
1049
1050
  metadata.contentCoverage = 'metadata_filename';
1050
1051
  metadata.ocrRecommended = true;
@@ -1143,7 +1144,7 @@ process.stdout.write(JSON.stringify({ pageCount: doc.numPages, pageLimit, text:
1143
1144
  }
1144
1145
  }
1145
1146
  async extractPdfText(buffer) {
1146
- // Tier 1: pdfjs-dist 文本提取(处理压缩内容流、CJK 字体、现代 PDF)
1147
+ // 第一层:pdfjs-dist 文本提取(处理压缩内容流、CJK 字体、现代 PDF)
1147
1148
  try {
1148
1149
  const mod = await resolveAndImport('pdfjs-dist/legacy/build/pdf.mjs');
1149
1150
  const loadingTask = mod.getDocument({ data: new Uint8Array(buffer), verbosity: 0 });
@@ -1171,7 +1172,7 @@ process.stdout.write(JSON.stringify({ pageCount: doc.numPages, pageLimit, text:
1171
1172
  if (process.env.KB_DEBUG === '1')
1172
1173
  console.warn('[kb] pdfjs-dist extraction failed:', e.message);
1173
1174
  }
1174
- // Tier 2: pdf-parse(兼容旧版 PDF)
1175
+ // 第二层:pdf-parse(兼容旧版 PDF)
1175
1176
  try {
1176
1177
  const mod = await resolveAndImport('pdf-parse');
1177
1178
  const pdfParse = mod.default;
@@ -1182,9 +1183,9 @@ process.stdout.write(JSON.stringify({ pageCount: doc.numPages, pageLimit, text:
1182
1183
  }
1183
1184
  }
1184
1185
  catch {
1185
- // fallback to raw regex below
1186
+ // 降级到下方纯正则提取
1186
1187
  }
1187
- // Tier 3: raw regex 回退(未压缩的古老 PDF)
1188
+ // 第三层:raw regex 回退(未压缩的古老 PDF)
1188
1189
  const raw = buffer.toString('latin1');
1189
1190
  const matches = Array.from(raw.matchAll(/\(([^()]{2,500})\)\s*T[jJ]/gu), match => match[1] ?? '')
1190
1191
  .concat(Array.from(raw.matchAll(/\[([^\]]{2,2000})\]\s*TJ/gu), match => match[1] ?? ''));
@@ -1,9 +1,11 @@
1
1
  import type { ClassifiedFile, FileCategory } from '../types.js';
2
+ /** 外部解析器提取结果 */
2
3
  export interface ExternalExtractionResult {
3
4
  text: string;
4
5
  metadata?: Record<string, unknown>;
5
6
  warnings?: string[];
6
7
  }
8
+ /** 外部解析器接口 */
7
9
  export interface ExternalExtractor {
8
10
  readonly id: string;
9
11
  readonly name: string;
@@ -15,6 +17,7 @@ export interface ExternalExtractor {
15
17
  extract(file: ClassifiedFile): ExternalExtractionResult;
16
18
  describe(): ExternalExtractorCapability;
17
19
  }
20
+ /** 外部解析器能力描述 */
18
21
  export interface ExternalExtractorCapability {
19
22
  id: string;
20
23
  name: string;
@@ -24,6 +27,7 @@ export interface ExternalExtractorCapability {
24
27
  available: boolean;
25
28
  kind: 'command';
26
29
  }
30
+ /** 命令行外部解析器配置选项 */
27
31
  export interface CommandExternalExtractorOptions {
28
32
  id: string;
29
33
  name: string;
@@ -34,6 +38,7 @@ export interface CommandExternalExtractorOptions {
34
38
  extensions?: string[];
35
39
  timeoutMs?: number;
36
40
  }
41
+ /** 命令行外部解析器,通过调用外部命令提取文件内容 */
37
42
  export declare class CommandExternalExtractor implements ExternalExtractor {
38
43
  readonly id: string;
39
44
  readonly name: string;
@@ -51,8 +56,13 @@ export declare class CommandExternalExtractor implements ExternalExtractor {
51
56
  private interpolate;
52
57
  private checkAvailable;
53
58
  }
59
+ /** 外部解析器注册表,管理所有已注册的外部解析器 */
54
60
  export declare class ExternalExtractorRegistry {
55
61
  private readonly extractors;
62
+ /**
63
+ * 根据环境变量自动注册可用解析器
64
+ * @param env 环境变量(默认 process.env)
65
+ */
56
66
  static fromEnvironment(env?: NodeJS.ProcessEnv): ExternalExtractorRegistry;
57
67
  register(extractor: ExternalExtractor): void;
58
68
  findAll(file: ClassifiedFile): ExternalExtractor[];
@@ -1,5 +1,6 @@
1
1
  import { spawnSync } from 'node:child_process';
2
2
  import * as path from 'node:path';
3
+ /** 命令行外部解析器,通过调用外部命令提取文件内容 */
3
4
  export class CommandExternalExtractor {
4
5
  id;
5
6
  name;
@@ -87,8 +88,13 @@ export class CommandExternalExtractor {
87
88
  return !result.error;
88
89
  }
89
90
  }
91
+ /** 外部解析器注册表,管理所有已注册的外部解析器 */
90
92
  export class ExternalExtractorRegistry {
91
93
  extractors = [];
94
+ /**
95
+ * 根据环境变量自动注册可用解析器
96
+ * @param env 环境变量(默认 process.env)
97
+ */
92
98
  static fromEnvironment(env = process.env) {
93
99
  const registry = new ExternalExtractorRegistry();
94
100
  registry.registerConfiguredOrAuto('cad-dwg', 'DWG Advanced Parser', env.CUSTOMIZE_AGENT_DWG_PARSER, [{ command: 'dwg-parser' }, { command: 'oda-dwg-parser' }], { category: 'cad', formats: ['autocad'], extensions: ['.dwg', '.dwt'] });
@@ -37,7 +37,7 @@ function findInPnpm(packageName) {
37
37
  }
38
38
  }
39
39
  }
40
- catch { /* continue upward */ }
40
+ catch { /* 继续向上遍历 */ }
41
41
  }
42
42
  const parent = path.dirname(dir);
43
43
  if (parent === dir)
@@ -54,7 +54,7 @@ export function resolvePackage(specifier) {
54
54
  try {
55
55
  return localRequire.resolve(specifier);
56
56
  }
57
- catch { /* fall through */ }
57
+ catch { /* 继续尝试后续回退方式 */ }
58
58
  const parts = specifier.split('/');
59
59
  let packageName;
60
60
  if (parts[0]?.startsWith('@')) {
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- // Types and constants from types.ts and constants.ts are not exported
2
- // as they are only used internally within the knowledge package.
1
+ // types.ts constants.ts 中的类型和常量不对外导出
2
+ // 它们仅在 knowledge 包内部使用。
3
3
  export { TextChunker } from './chunking/text-chunker.js';
4
4
  export { FileClassifier } from './classification/classifier.js';
5
5
  export { DedupEngine } from './dedup/dedup-engine.js';
@@ -1,5 +1,7 @@
1
1
  import type { VectorStoreInterface } from '../vector/types.js';
2
+ /** 搜索范围:项目级、全局级或全部 */
2
3
  export type SearchScope = 'project' | 'global' | 'all';
4
+ /** 联合搜索结果项 */
3
5
  export interface FederatedSearchItem {
4
6
  id: string;
5
7
  rowid?: number;
@@ -26,6 +28,7 @@ export interface FederatedSearchItem {
26
28
  };
27
29
  facets?: Record<string, string | number | string[]>;
28
30
  }
31
+ /** 联合搜索结果 */
29
32
  export interface FederatedResult {
30
33
  results: FederatedSearchItem[];
31
34
  scopesSearched: Array<'project' | 'global'>;
@@ -38,16 +41,19 @@ export interface FederatedResult {
38
41
  reranker?: string;
39
42
  };
40
43
  }
44
+ /** 搜索过滤器 */
41
45
  export interface SearchFilters {
42
46
  category?: string;
43
47
  filePath?: string;
44
48
  }
49
+ /** 各检索方式权重配置 */
45
50
  export interface RetrievalWeights {
46
51
  keyword?: number;
47
52
  vector?: number;
48
53
  rewrite?: number;
49
54
  hybridBonus?: number;
50
55
  }
56
+ /** 联合搜索查询参数 */
51
57
  export interface FederatedQuery {
52
58
  query: string;
53
59
  queryEmbedding: number[];
@@ -57,6 +63,7 @@ export interface FederatedQuery {
57
63
  collections?: string[];
58
64
  filters?: SearchFilters;
59
65
  }
66
+ /** 联合搜索器,支持跨向量存储的分布式搜索和结果合并 */
60
67
  export declare class FederationSearch {
61
68
  private readonly vectorStores;
62
69
  static readonly SCOPE_WEIGHTS: Record<'project' | 'global', number>;
@@ -1,5 +1,6 @@
1
1
  import { ALL_CATEGORIES } from '../constants.js';
2
2
  import { globalCollectionName, projectCollectionName } from '../vector/collection-manager.js';
3
+ /** 联合搜索器,支持跨向量存储的分布式搜索和结果合并 */
3
4
  export class FederationSearch {
4
5
  vectorStores;
5
6
  static SCOPE_WEIGHTS = {
package/dist/types.d.ts CHANGED
@@ -1,6 +1,10 @@
1
+ /** 知识库作用域:项目级、全局级或会话级 */
1
2
  export type KBScope = 'project' | 'global' | 'session';
3
+ /** 文件分类枚举 */
2
4
  export type FileCategory = 'document' | 'spreadsheet' | 'image' | 'cad' | 'code' | 'data' | 'web' | 'diagram' | 'archive' | 'other';
5
+ /** 项目状态:活跃、空闲或错误 */
3
6
  export type ProjectStatus = 'active' | 'idle' | 'error';
7
+ /** 项目信息 */
4
8
  export interface ProjectInfo {
5
9
  projectId: string;
6
10
  projectRoot: string;
@@ -13,6 +17,7 @@ export interface ProjectInfo {
13
17
  lastOpenedAt: number;
14
18
  status: ProjectStatus;
15
19
  }
20
+ /** 项目配置 */
16
21
  export interface ProjectConfig {
17
22
  projectId: string;
18
23
  projectName?: string;
@@ -27,6 +32,7 @@ export interface ProjectConfig {
27
32
  createdAt: number;
28
33
  lastOpenedAt: number;
29
34
  }
35
+ /** 已分类的文件信息 */
30
36
  export interface ClassifiedFile {
31
37
  absolutePath: string;
32
38
  relativePath: string;
@@ -36,6 +42,7 @@ export interface ClassifiedFile {
36
42
  mtime: number;
37
43
  mimeType: string;
38
44
  }
45
+ /** 索引状态记录 */
39
46
  export interface IndexStateRecord {
40
47
  relativePath: string;
41
48
  category: FileCategory;
@@ -51,6 +58,7 @@ export interface IndexStateRecord {
51
58
  errorMessage?: string;
52
59
  metadataJson?: string;
53
60
  }
61
+ /** 文件差异对比结果 */
54
62
  export interface DiffResult {
55
63
  newFiles: ClassifiedFile[];
56
64
  modifiedFiles: ClassifiedFile[];
@@ -64,6 +72,7 @@ export interface DiffResult {
64
72
  hasChanges: boolean;
65
73
  diffTimeMs: number;
66
74
  }
75
+ /** 知识库统计信息 */
67
76
  export interface KnowledgeBaseStats {
68
77
  scope: Exclude<KBScope, 'session'>;
69
78
  projectId?: string;
@@ -72,6 +81,7 @@ export interface KnowledgeBaseStats {
72
81
  totalSizeBytes: number;
73
82
  lastIndexedAt: number;
74
83
  }
84
+ /** 跨项目重复文件检测结果 */
75
85
  export interface CrossProjectDuplicate {
76
86
  contentHash: string;
77
87
  files: Array<{
@@ -1,7 +1,10 @@
1
1
  import type { FileCategory } from '../types.js';
2
2
  import type { CollectionClient } from './types.js';
3
+ /** 生成项目级 Collection 名称 */
3
4
  export declare function projectCollectionName(projectId: string, category: FileCategory): string;
5
+ /** 生成全局 Collection 名称 */
4
6
  export declare function globalCollectionName(category: FileCategory): string;
7
+ /** Collection 管理器,负责 Vector Collection 的创建和管理 */
5
8
  export declare class CollectionManager {
6
9
  private readonly client?;
7
10
  constructor(client?: CollectionClient | undefined);
@@ -1,10 +1,13 @@
1
1
  import { ALL_CATEGORIES, COLLECTION_CATEGORY_NAMES } from '../constants.js';
2
+ /** 生成项目级 Collection 名称 */
2
3
  export function projectCollectionName(projectId, category) {
3
4
  return `proj_${projectId}_kb_${COLLECTION_CATEGORY_NAMES[category]}`;
4
5
  }
6
+ /** 生成全局 Collection 名称 */
5
7
  export function globalCollectionName(category) {
6
8
  return `global_kb_${COLLECTION_CATEGORY_NAMES[category]}`;
7
9
  }
10
+ /** Collection 管理器,负责 Vector Collection 的创建和管理 */
8
11
  export class CollectionManager {
9
12
  client;
10
13
  constructor(client) {
@@ -1,4 +1,5 @@
1
1
  import type { VectorDocument, VectorSearchQuery, VectorSearchResult, VectorStoreInterface } from './types.js';
2
+ /** HNSW(分层可导航小世界图)向量存储,基于 hnswlib-node 实现的高效近似最近邻搜索 */
2
3
  export declare class HNSWVectorStore implements VectorStoreInterface {
3
4
  readonly collectionName: string;
4
5
  private readonly indexPath;
@@ -2,6 +2,7 @@ import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import { createRequire } from 'node:module';
4
4
  const require = createRequire(import.meta.url);
5
+ /** HNSW(分层可导航小世界图)向量存储,基于 hnswlib-node 实现的高效近似最近邻搜索 */
5
6
  export class HNSWVectorStore {
6
7
  collectionName;
7
8
  indexPath;
@@ -57,7 +58,7 @@ export class HNSWVectorStore {
57
58
  this.index.markDelete(rowid);
58
59
  this.deletedSinceRebuild += 1;
59
60
  }
60
- catch { /* ignore missing labels */ }
61
+ catch { /* 忽略缺失的标签 */ }
61
62
  this.documents.delete(rowid);
62
63
  }
63
64
  }
@@ -1,24 +1,29 @@
1
+ /** 向量文档,包含文本内容、嵌入向量和元数据 */
1
2
  export interface VectorDocument {
2
3
  id: string;
3
4
  content: string;
4
5
  embedding: number[];
5
6
  metadata: Record<string, string | number | boolean | null>;
6
7
  }
8
+ /** 向量搜索查询参数 */
7
9
  export interface VectorSearchQuery {
8
10
  queryEmbedding: number[];
9
11
  topK: number;
10
12
  where?: Record<string, string | number | boolean>;
11
13
  }
14
+ /** 向量搜索结果 */
12
15
  export interface VectorSearchResult {
13
16
  document: Omit<VectorDocument, 'embedding'>;
14
17
  score: number;
15
18
  collection: string;
16
19
  }
20
+ /** Vector Collection 信息 */
17
21
  export interface VectorCollectionInfo {
18
22
  id?: string;
19
23
  name: string;
20
24
  metadata?: Record<string, unknown>;
21
25
  }
26
+ /** 向量存储接口,定义所有向量存储实现必须支持的方法 */
22
27
  export interface VectorStoreInterface {
23
28
  readonly collectionName: string;
24
29
  ensureCollection(metadata?: Record<string, unknown>): Promise<void>;
@@ -28,6 +33,7 @@ export interface VectorStoreInterface {
28
33
  needsRebuild?(): boolean;
29
34
  search(query: VectorSearchQuery): Promise<VectorSearchResult[]>;
30
35
  }
36
+ /** Collection Client 接口,用于管理远程 Vector Collection */
31
37
  export interface CollectionClient {
32
38
  getOrCreateCollection(name: string, metadata?: Record<string, unknown>): Promise<VectorCollectionInfo>;
33
39
  listCollections(): Promise<VectorCollectionInfo[]>;
@@ -1,17 +1,29 @@
1
1
  import type { EmbeddingProvider } from '../embedding/embedding-provider.js';
2
2
  import type { StoredChunk } from '../core/index-state-store.js';
3
3
  import type { VectorStoreInterface } from './types.js';
4
+ /** 向量索引结果 */
4
5
  export interface VectorIndexResult {
5
6
  collectionName: string;
6
7
  chunkCount: number;
7
8
  embeddingModel: string;
8
9
  embeddingDimension: number;
9
10
  }
11
+ export interface VectorIndexProgress {
12
+ collectionName: string;
13
+ processedChunks: number;
14
+ totalChunks: number;
15
+ batchSize: number;
16
+ }
17
+ export interface VectorIndexOptions {
18
+ batchSize?: number;
19
+ onProgress?: (progress: VectorIndexProgress) => void;
20
+ }
21
+ /** 向量索引器,负责将文本切片生成 Embedding 并写入向量存储 */
10
22
  export declare class VectorIndexer {
11
23
  private readonly embeddingProvider;
12
24
  private readonly vectorStores;
13
25
  constructor(embeddingProvider: EmbeddingProvider, vectorStores: Map<string, VectorStoreInterface>);
14
- indexChunks(chunks: StoredChunk[]): Promise<VectorIndexResult[]>;
26
+ indexChunks(chunks: StoredChunk[], options?: VectorIndexOptions): Promise<VectorIndexResult[]>;
15
27
  deleteFile(collectionName: string, filePath: string): Promise<void>;
16
28
  private embedDocuments;
17
29
  private isValidEmbeddings;
@@ -1,3 +1,10 @@
1
+ function resolveVectorIndexBatchSize(configured) {
2
+ const raw = configured ?? Number(process.env.CUSTOMIZE_VECTOR_INDEX_BATCH_SIZE ?? process.env.KB_VECTOR_INDEX_BATCH_SIZE);
3
+ if (!Number.isFinite(raw) || raw <= 0)
4
+ return 32;
5
+ return Math.max(1, Math.min(256, Math.floor(raw)));
6
+ }
7
+ /** 向量索引器,负责将文本切片生成 Embedding 并写入向量存储 */
1
8
  export class VectorIndexer {
2
9
  embeddingProvider;
3
10
  vectorStores;
@@ -5,24 +12,34 @@ export class VectorIndexer {
5
12
  this.embeddingProvider = embeddingProvider;
6
13
  this.vectorStores = vectorStores;
7
14
  }
8
- async indexChunks(chunks) {
15
+ async indexChunks(chunks, options = {}) {
9
16
  const byCollection = this.groupByCollection(chunks);
10
17
  const results = [];
18
+ const totalChunks = chunks.length;
19
+ let processedTotalChunks = 0;
11
20
  for (const [collectionName, collectionChunks] of byCollection) {
12
21
  const store = this.vectorStores.get(collectionName);
13
22
  if (!store)
14
23
  continue;
15
- const texts = collectionChunks.map(chunk => chunk.content);
16
- const embeddings = await this.embedDocuments(texts);
17
24
  await store.ensureCollection({
18
25
  embedding_model: this.embeddingProvider.model,
19
26
  embedding_dimension: this.embeddingProvider.dimensions,
20
27
  });
21
- const documents = collectionChunks.map((chunk, index) => this.toVectorDocument(chunk, embeddings[index] ?? []));
22
- await store.upsert(documents);
28
+ const batchSize = resolveVectorIndexBatchSize(options.batchSize);
29
+ let processedChunks = 0;
30
+ for (let offset = 0; offset < collectionChunks.length; offset += batchSize) {
31
+ const batchChunks = collectionChunks.slice(offset, offset + batchSize);
32
+ const texts = batchChunks.map(chunk => chunk.content);
33
+ const embeddings = await this.embedDocuments(texts);
34
+ const documents = batchChunks.map((chunk, index) => this.toVectorDocument(chunk, embeddings[index] ?? []));
35
+ await store.upsert(documents);
36
+ processedChunks += documents.length;
37
+ processedTotalChunks += documents.length;
38
+ options.onProgress?.({ collectionName, processedChunks: processedTotalChunks, totalChunks, batchSize: documents.length });
39
+ }
23
40
  results.push({
24
41
  collectionName,
25
- chunkCount: documents.length,
42
+ chunkCount: processedChunks,
26
43
  embeddingModel: this.embeddingProvider.model,
27
44
  embeddingDimension: this.embeddingProvider.dimensions,
28
45
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@customize-agent/knowledge",
3
- "version": "4.0.2",
3
+ "version": "4.0.4",
4
4
  "description": "Local knowledge base infrastructure for customize-agent",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",