@customize-agent/knowledge 3.0.3 → 3.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.
@@ -53,7 +53,7 @@ export class FileClassifier {
53
53
  ['.txt', 'document', 'plaintext'], ['.textclipping', 'document', 'text_clipping'], ['.rst', 'document', 'plaintext'], ['.asciidoc', 'document', 'plaintext'], ['.tex', 'document', 'plaintext'],
54
54
  ['.epub', 'document', 'ebook'], ['.mobi', 'document', 'ebook'],
55
55
  ['.xlsx', 'spreadsheet', 'excel'], ['.xls', 'spreadsheet', 'excel'], ['.xlsm', 'spreadsheet', 'excel'],
56
- ['.csv', 'spreadsheet', 'csv'], ['.tsv', 'spreadsheet', 'csv'], ['.tab', 'spreadsheet', 'csv'], ['.ods', 'spreadsheet', 'opendoc'],
56
+ ['.csv', 'spreadsheet', 'csv'], ['.tsv', 'spreadsheet', 'tsv'], ['.tab', 'spreadsheet', 'tsv'], ['.ods', 'spreadsheet', 'opendoc'],
57
57
  ['.png', 'image', 'raster'], ['.jpg', 'image', 'raster'], ['.jpeg', 'image', 'raster'], ['.gif', 'image', 'raster'], ['.bmp', 'image', 'raster'], ['.webp', 'image', 'raster'], ['.tiff', 'image', 'raster'], ['.tif', 'image', 'raster'],
58
58
  ['.svg', 'image', 'vector'], ['.eps', 'image', 'vector'], ['.raw', 'image', 'raw'], ['.cr2', 'image', 'raw'], ['.nef', 'image', 'raw'], ['.dng', 'image', 'raw'],
59
59
  ['.dwg', 'cad', 'autocad'], ['.dxf', 'cad', 'autocad'], ['.dwt', 'cad', 'autocad'],
@@ -30,11 +30,14 @@ export class ChangeTracker {
30
30
  continue;
31
31
  }
32
32
  const metadata = this.parseMetadata(indexed.metadataJson);
33
+ const extraction = metadata.extraction && typeof metadata.extraction === 'object' ? metadata.extraction : {};
34
+ const contentCoverage = metadata.contentCoverage ?? extraction.contentCoverage;
35
+ const extractionMode = metadata.extractionMode ?? extraction.extractionMode;
33
36
  const needsReindex = indexed.status === 'error'
34
37
  || indexed.chunkCount === 0
35
38
  || (classified.format === 'pdf' && indexed.chunkCount <= 1)
36
- || metadata.contentCoverage === 'metadata_filename'
37
- || metadata.extractionMode === 'pdf_metadata_only';
39
+ || contentCoverage === 'metadata_filename'
40
+ || extractionMode === 'pdf_metadata_only';
38
41
  if (needsReindex) {
39
42
  modifiedFiles.push(classified);
40
43
  continue;
@@ -6,7 +6,7 @@ import { FileClassifier } from '../classification/classifier.js';
6
6
  import { ALL_CATEGORIES, DEFAULT_CATEGORY_DIRS, GLOBAL_KNOWLEDGE_DIR, USER_DATA_DIR } from '../constants.js';
7
7
  import { DedupEngine } from '../dedup/dedup-engine.js';
8
8
  import { RelationshipDetector } from '../dedup/relationship-detector.js';
9
- import { HashEmbeddingProvider } from '../embedding/embedding-provider.js';
9
+ import { createEmbeddingProviderFromEnvironment } from '../embedding/embedding-provider.js';
10
10
  import { ContentExtractor } from '../extraction/content-extractor.js';
11
11
  import { FederationSearch } from '../search/federation-search.js';
12
12
  import { CollectionManager } from '../vector/collection-manager.js';
@@ -41,7 +41,7 @@ export class KnowledgeBaseManager {
41
41
  this.scope = options.scope;
42
42
  this.projectRoot = options.projectRoot;
43
43
  this.projectId = options.projectId;
44
- this.embeddingProvider = options.embeddingProvider ?? new HashEmbeddingProvider();
44
+ this.embeddingProvider = options.embeddingProvider ?? createEmbeddingProviderFromEnvironment();
45
45
  this.vectorStores = options.vectorStores ?? new Map();
46
46
  this.extractor = new ContentExtractor(options.externalExtractors);
47
47
  this.llmProvider = options.llmProvider;
@@ -200,6 +200,7 @@ export class KnowledgeBaseManager {
200
200
  status: 'active',
201
201
  metadataJson: JSON.stringify({
202
202
  mimeType: file.mimeType,
203
+ ...extraction.metadata,
203
204
  extraction: extraction.metadata,
204
205
  warnings: extraction.warnings,
205
206
  extractionTimeMs: extraction.extractionTimeMs,
@@ -705,7 +706,7 @@ ${resultsText}
705
706
  }
706
707
  hasUsableContent(text, metadata) {
707
708
  const coverage = String(metadata.contentCoverage ?? '');
708
- if (coverage === 'metadata')
709
+ if (['metadata', 'metadata_filename', 'pdf_metadata_only', 'office_zip_empty_text', 'office_zip_failed'].includes(coverage))
709
710
  return false;
710
711
  return text.trim().length > 0;
711
712
  }
@@ -14,3 +14,20 @@ export declare class HashEmbeddingProvider implements EmbeddingProvider {
14
14
  private tokenize;
15
15
  private normalize;
16
16
  }
17
+ export interface OpenAICompatibleEmbeddingOptions {
18
+ baseUrl: string;
19
+ apiKey?: string;
20
+ model: string;
21
+ dimensions?: number;
22
+ }
23
+ export declare class OpenAICompatibleEmbeddingProvider implements EmbeddingProvider {
24
+ readonly model: string;
25
+ readonly dimensions: number;
26
+ private readonly baseUrl;
27
+ private readonly apiKey?;
28
+ constructor(options: OpenAICompatibleEmbeddingOptions);
29
+ embedDocuments(texts: string[]): Promise<number[][]>;
30
+ embedQuery(text: string): Promise<number[]>;
31
+ private embed;
32
+ }
33
+ export declare function createEmbeddingProviderFromEnvironment(): EmbeddingProvider;
@@ -1,4 +1,7 @@
1
1
  import * as crypto from 'node:crypto';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
2
5
  export class HashEmbeddingProvider {
3
6
  dimensions;
4
7
  model = 'hash-embedding-local';
@@ -44,3 +47,66 @@ export class HashEmbeddingProvider {
44
47
  return vector.map(value => value / norm);
45
48
  }
46
49
  }
50
+ export class OpenAICompatibleEmbeddingProvider {
51
+ model;
52
+ dimensions;
53
+ baseUrl;
54
+ apiKey;
55
+ constructor(options) {
56
+ this.baseUrl = options.baseUrl.replace(/\/+$/u, '');
57
+ this.apiKey = options.apiKey;
58
+ this.model = options.model;
59
+ this.dimensions = options.dimensions ?? 1024;
60
+ }
61
+ async embedDocuments(texts) {
62
+ return this.embed(texts);
63
+ }
64
+ async embedQuery(text) {
65
+ return (await this.embed([text]))[0] ?? [];
66
+ }
67
+ async embed(input) {
68
+ const response = await fetch(`${this.baseUrl}/embeddings`, {
69
+ method: 'POST',
70
+ headers: {
71
+ 'Content-Type': 'application/json',
72
+ ...(this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}),
73
+ },
74
+ body: JSON.stringify({ model: this.model, input }),
75
+ });
76
+ if (!response.ok)
77
+ throw new Error(`Embedding request failed: HTTP ${response.status} ${await response.text()}`);
78
+ const payload = await response.json();
79
+ return (payload.data ?? []).map(item => item.embedding ?? []);
80
+ }
81
+ }
82
+ function readStoredEmbeddingConfig() {
83
+ try {
84
+ const configPath = path.join(os.homedir(), '.customize-agent', 'config.json');
85
+ if (!fs.existsSync(configPath))
86
+ return undefined;
87
+ const raw = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
88
+ return raw.embedding;
89
+ }
90
+ catch {
91
+ return undefined;
92
+ }
93
+ }
94
+ export function createEmbeddingProviderFromEnvironment() {
95
+ const stored = readStoredEmbeddingConfig();
96
+ const provider = process.env.CUSTOMIZE_EMBEDDING_PROVIDER ?? process.env.KB_EMBEDDING_PROVIDER ?? stored?.provider;
97
+ if (provider === 'openai-compatible') {
98
+ const baseUrl = process.env.CUSTOMIZE_EMBEDDING_BASE_URL ?? process.env.KB_EMBEDDING_BASE_URL ?? stored?.baseUrl;
99
+ const model = process.env.CUSTOMIZE_EMBEDDING_MODEL ?? process.env.KB_EMBEDDING_MODEL ?? stored?.model;
100
+ if (baseUrl && model) {
101
+ const rawDimensions = process.env.CUSTOMIZE_EMBEDDING_DIMENSIONS ?? process.env.KB_EMBEDDING_DIMENSIONS;
102
+ const dimensions = Number(rawDimensions ?? stored?.dimensions ?? 1024);
103
+ return new OpenAICompatibleEmbeddingProvider({
104
+ baseUrl,
105
+ model,
106
+ apiKey: process.env.CUSTOMIZE_EMBEDDING_API_KEY ?? process.env.KB_EMBEDDING_API_KEY ?? stored?.apiKey,
107
+ dimensions: Number.isFinite(dimensions) ? dimensions : 1024,
108
+ });
109
+ }
110
+ }
111
+ return new HashEmbeddingProvider();
112
+ }
@@ -19,88 +19,91 @@ export class ContentExtractor {
19
19
  format: file.format,
20
20
  };
21
21
  const external = this.tryExternalExtractor(file);
22
- if (external) {
22
+ if (external?.text.trim()) {
23
23
  text = external.text;
24
24
  Object.assign(metadata, external.metadata);
25
25
  warnings.push(...external.warnings);
26
26
  }
27
- else if (file.category === 'cad') {
28
- const result = await this.extractCad(file);
29
- text = result.text;
30
- Object.assign(metadata, result.metadata);
31
- warnings.push(...result.warnings);
32
- }
33
- else if (file.category === 'data') {
34
- const result = this.extractData(file);
35
- text = result.text;
36
- Object.assign(metadata, result.metadata);
37
- warnings.push(...result.warnings);
38
- }
39
- else if (file.category === 'diagram') {
40
- const result = this.extractDiagram(file);
41
- text = result.text;
42
- Object.assign(metadata, result.metadata);
43
- warnings.push(...result.warnings);
44
- }
45
- else if (file.category === 'document' && file.format === 'pdf') {
46
- const result = await this.extractPdf(file);
47
- text = result.text;
48
- Object.assign(metadata, result.metadata);
49
- warnings.push(...result.warnings);
50
- }
51
- else if (file.category === 'document' && ['office', 'presentation'].includes(file.format)) {
52
- const result = await this.extractOfficeDocument(file);
53
- text = result.text;
54
- Object.assign(metadata, result.metadata);
55
- warnings.push(...result.warnings);
56
- }
57
- else if (file.category === 'spreadsheet' && ['csv', 'tsv'].includes(file.format)) {
58
- const result = this.extractDelimitedText(file);
59
- text = result.text;
60
- Object.assign(metadata, result.metadata);
61
- warnings.push(...result.warnings);
62
- }
63
- else if (file.category === 'spreadsheet') {
64
- const result = await this.extractSpreadsheet(file);
65
- text = result.text;
66
- Object.assign(metadata, result.metadata);
67
- warnings.push(...result.warnings);
68
- }
69
- else if (file.category === 'archive') {
70
- const result = await this.extractArchive(file);
71
- text = result.text;
72
- Object.assign(metadata, result.metadata);
73
- warnings.push(...result.warnings);
74
- }
75
- else if (file.category === 'image' && file.format !== 'vector') {
76
- const result = await this.extractRasterImage(file);
77
- text = result.text;
78
- Object.assign(metadata, result.metadata);
79
- warnings.push(...result.warnings);
80
- }
81
- else if (file.category === 'image' && file.format === 'vector') {
82
- const result = this.extractSvg(file);
83
- text = result.text;
84
- Object.assign(metadata, result.metadata);
85
- warnings.push(...result.warnings);
86
- }
87
- else if (file.format === 'text_clipping') {
88
- const result = this.extractTextClipping(file);
89
- text = result.text;
90
- Object.assign(metadata, result.metadata);
91
- warnings.push(...result.warnings);
92
- }
93
- else if (this.isTextReadable(file)) {
94
- text = fs.readFileSync(file.absolutePath, 'utf8');
95
- metadata.extractionMode = 'plain_text';
96
- metadata.vectorizable = true;
97
- }
98
27
  else {
99
- text = this.metadataOnlyText(file);
100
- metadata.extractionMode = 'metadata_only';
101
- metadata.vectorizable = true;
102
- metadata.contentCoverage = 'metadata';
103
- warnings.push(`暂不支持 ${file.category}/${file.format} 内容提取,未解析出正文,未入库`);
28
+ warnings.push(...(external?.warnings ?? []));
29
+ if (file.category === 'cad') {
30
+ const result = await this.extractCad(file);
31
+ text = result.text;
32
+ Object.assign(metadata, result.metadata);
33
+ warnings.push(...result.warnings);
34
+ }
35
+ else if (file.category === 'data') {
36
+ const result = this.extractData(file);
37
+ text = result.text;
38
+ Object.assign(metadata, result.metadata);
39
+ warnings.push(...result.warnings);
40
+ }
41
+ else if (file.category === 'diagram') {
42
+ const result = this.extractDiagram(file);
43
+ text = result.text;
44
+ Object.assign(metadata, result.metadata);
45
+ warnings.push(...result.warnings);
46
+ }
47
+ else if (file.category === 'document' && file.format === 'pdf') {
48
+ const result = await this.extractPdf(file);
49
+ text = result.text;
50
+ Object.assign(metadata, result.metadata);
51
+ warnings.push(...result.warnings);
52
+ }
53
+ else if (file.category === 'document' && ['office', 'presentation'].includes(file.format)) {
54
+ const result = await this.extractOfficeDocument(file);
55
+ text = result.text;
56
+ Object.assign(metadata, result.metadata);
57
+ warnings.push(...result.warnings);
58
+ }
59
+ else if (file.category === 'spreadsheet' && ['csv', 'tsv'].includes(file.format)) {
60
+ const result = this.extractDelimitedText(file);
61
+ text = result.text;
62
+ Object.assign(metadata, result.metadata);
63
+ warnings.push(...result.warnings);
64
+ }
65
+ else if (file.category === 'spreadsheet') {
66
+ const result = await this.extractSpreadsheet(file);
67
+ text = result.text;
68
+ Object.assign(metadata, result.metadata);
69
+ warnings.push(...result.warnings);
70
+ }
71
+ else if (file.category === 'archive') {
72
+ const result = await this.extractArchive(file);
73
+ text = result.text;
74
+ Object.assign(metadata, result.metadata);
75
+ warnings.push(...result.warnings);
76
+ }
77
+ else if (file.category === 'image' && file.format !== 'vector') {
78
+ const result = await this.extractRasterImage(file);
79
+ text = result.text;
80
+ Object.assign(metadata, result.metadata);
81
+ warnings.push(...result.warnings);
82
+ }
83
+ else if (file.category === 'image' && file.format === 'vector') {
84
+ const result = this.extractSvg(file);
85
+ text = result.text;
86
+ Object.assign(metadata, result.metadata);
87
+ warnings.push(...result.warnings);
88
+ }
89
+ else if (file.format === 'text_clipping') {
90
+ const result = this.extractTextClipping(file);
91
+ text = result.text;
92
+ Object.assign(metadata, result.metadata);
93
+ warnings.push(...result.warnings);
94
+ }
95
+ else if (this.isTextReadable(file)) {
96
+ text = fs.readFileSync(file.absolutePath, 'utf8');
97
+ metadata.extractionMode = 'plain_text';
98
+ metadata.vectorizable = true;
99
+ }
100
+ else {
101
+ text = this.metadataOnlyText(file);
102
+ metadata.extractionMode = 'metadata_only';
103
+ metadata.vectorizable = true;
104
+ metadata.contentCoverage = 'metadata';
105
+ warnings.push(`暂不支持 ${file.category}/${file.format} 内容提取,未解析出正文,未入库`);
106
+ }
104
107
  }
105
108
  return {
106
109
  text: text.trim(),
@@ -138,7 +141,7 @@ export class ContentExtractor {
138
141
  warnings.push(`外部解析器 ${extractor.name} 失败: ${error instanceof Error ? error.message : String(error)}`);
139
142
  }
140
143
  }
141
- return undefined;
144
+ return warnings.length > 0 ? { text: '', metadata: {}, warnings } : undefined;
142
145
  }
143
146
  extractTextClipping(file) {
144
147
  const buffer = fs.readFileSync(file.absolutePath);
@@ -282,7 +285,7 @@ export class ContentExtractor {
282
285
  metadata.contentCoverage = readable.length > 0 ? 'cad_readable_text_fragments' : 'metadata';
283
286
  metadata.stringCount = readable.length;
284
287
  if (readable.length === 0)
285
- warnings.push(`${file.format} 内置 CAD 解析器未提取到可用文本,已入库文件元数据用于检索`);
288
+ warnings.push(`${file.format} 内置 CAD 解析器未提取到可用文本,仅记录文件元数据,未生成可检索正文切片`);
286
289
  else
287
290
  warnings.push(`${file.format} 未检测到专业 DWG 转换器,已使用内置可读标注/标题块抽取;如需完整图纸结构,请安装 ODA File Converter 或 LibreDWG 并配置外部解析器`);
288
291
  return {
@@ -336,18 +339,34 @@ export class ContentExtractor {
336
339
  try {
337
340
  const customCmd = process.env.CUSTOMIZE_DWG_TO_DXF_CMD;
338
341
  if (customCmd) {
339
- const command = customCmd.replace(/\{input\}/gu, filePath).replace(/\{output\}/gu, outputPath);
340
- const result = spawnSync(command, { shell: true, encoding: 'utf8', timeout: 120_000 });
342
+ if (/\s/u.test(customCmd))
343
+ return { tool: 'external_dwg_to_dxf', warnings: ['CUSTOMIZE_DWG_TO_DXF_CMD 只支持可执行文件路径;参数请使用 CUSTOMIZE_DWG_TO_DXF_ARGS JSON 数组配置'] };
344
+ let argTemplate = ['{input}', '{output}'];
345
+ try {
346
+ if (process.env.CUSTOMIZE_DWG_TO_DXF_ARGS)
347
+ argTemplate = JSON.parse(process.env.CUSTOMIZE_DWG_TO_DXF_ARGS);
348
+ }
349
+ catch {
350
+ return { tool: 'external_dwg_to_dxf', warnings: ['CUSTOMIZE_DWG_TO_DXF_ARGS 必须是字符串数组 JSON'] };
351
+ }
352
+ if (!Array.isArray(argTemplate) || !argTemplate.every(arg => typeof arg === 'string'))
353
+ return { tool: 'external_dwg_to_dxf', warnings: ['CUSTOMIZE_DWG_TO_DXF_ARGS 必须是字符串数组 JSON'] };
354
+ const args = argTemplate.map(arg => arg.replace(/\{input\}/gu, filePath).replace(/\{output\}/gu, outputPath));
355
+ const result = spawnSync(customCmd, args, { shell: false, encoding: 'utf8', timeout: 120_000 });
341
356
  if (result.status === 0 && fs.existsSync(outputPath))
342
357
  return { dxfText: fs.readFileSync(outputPath, 'utf8'), tool: 'external_dwg_to_dxf', warnings: [] };
343
358
  return { tool: 'external_dwg_to_dxf', warnings: [`CUSTOMIZE_DWG_TO_DXF_CMD 转换失败: ${result.stderr || result.stdout || result.error?.message || 'unknown error'}`] };
344
359
  }
360
+ const failures = [];
345
361
  for (const bin of ['dwgread', 'dwg2dxf']) {
346
362
  const result = spawnSync(bin, bin === 'dwgread' ? ['-O', 'DXF', '-o', outputPath, filePath] : [filePath, outputPath], { encoding: 'utf8', timeout: 120_000 });
347
363
  if (result.status === 0 && fs.existsSync(outputPath))
348
364
  return { dxfText: fs.readFileSync(outputPath, 'utf8'), tool: bin, warnings: [] };
365
+ if (result.error && 'code' in result.error && result.error.code === 'ENOENT')
366
+ continue;
367
+ failures.push(`${bin} 转换失败: ${result.stderr || result.stdout || result.error?.message || `exit ${result.status ?? 'unknown'}`}`);
349
368
  }
350
- return { tool: 'builtin_fallback', warnings: ['未检测到可用 DWG→DXF 转换器(dwgread/dwg2dxf/CUSTOMIZE_DWG_TO_DXF_CMD),使用内置图纸可读文本抽取'] };
369
+ return { tool: 'builtin_fallback', warnings: [...failures, failures.length ? 'DWG→DXF 转换失败,使用内置图纸可读文本抽取' : '未检测到可用 DWG→DXF 转换器(dwgread/dwg2dxf/CUSTOMIZE_DWG_TO_DXF_CMD),使用内置图纸可读文本抽取'] };
351
370
  }
352
371
  finally {
353
372
  fs.rmSync(tmpDir, { recursive: true, force: true });
@@ -559,6 +578,7 @@ export class ContentExtractor {
559
578
  };
560
579
  }
561
580
  async extractLegacyWordDocument(file) {
581
+ const warnings = [];
562
582
  try {
563
583
  const mod = await resolveAndImport('word-extractor');
564
584
  const WordExtractor = mod.default ?? mod;
@@ -571,11 +591,14 @@ export class ContentExtractor {
571
591
  warnings: [],
572
592
  };
573
593
  }
594
+ warnings.push('word-extractor 未提取到正文,已降级为二进制可读文本抽取');
574
595
  }
575
- catch {
576
- // fallback below
596
+ catch (error) {
597
+ warnings.push(`word-extractor 解析失败,已降级为二进制可读文本抽取: ${error instanceof Error ? error.message : String(error)}`);
577
598
  }
578
- return this.extractLegacyOfficeBinary(file);
599
+ const fallback = this.extractLegacyOfficeBinary(file);
600
+ fallback.warnings.unshift(...warnings);
601
+ return fallback;
579
602
  }
580
603
  extractLegacyOfficeBinary(file) {
581
604
  const strings = this.extractBinaryStrings(file.absolutePath).slice(0, 1_000);
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { TextChunker, type ChunkConfig, type TextChunk } from './chunking/text-c
2
2
  export { FileClassifier } from './classification/classifier.js';
3
3
  export { DedupEngine, type MinHashSignature, type SimilarityMatch } from './dedup/dedup-engine.js';
4
4
  export { RelationshipDetector } from './dedup/relationship-detector.js';
5
- export { HashEmbeddingProvider, type EmbeddingProvider } from './embedding/embedding-provider.js';
5
+ export { HashEmbeddingProvider, OpenAICompatibleEmbeddingProvider, createEmbeddingProviderFromEnvironment, type EmbeddingProvider, type OpenAICompatibleEmbeddingOptions } from './embedding/embedding-provider.js';
6
6
  export { ContentExtractor, type ExtractionResult } from './extraction/content-extractor.js';
7
7
  export { CommandExternalExtractor, ExternalExtractorRegistry, type CommandExternalExtractorOptions, type ExternalExtractionResult, type ExternalExtractor, type ExternalExtractorCapability } from './extraction/external-extractor.js';
8
8
  export { ChangeTracker } from './core/change-tracker.js';
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ export { TextChunker } from './chunking/text-chunker.js';
4
4
  export { FileClassifier } from './classification/classifier.js';
5
5
  export { DedupEngine } from './dedup/dedup-engine.js';
6
6
  export { RelationshipDetector } from './dedup/relationship-detector.js';
7
- export { HashEmbeddingProvider } from './embedding/embedding-provider.js';
7
+ export { HashEmbeddingProvider, OpenAICompatibleEmbeddingProvider, createEmbeddingProviderFromEnvironment } from './embedding/embedding-provider.js';
8
8
  export { ContentExtractor } from './extraction/content-extractor.js';
9
9
  export { CommandExternalExtractor, ExternalExtractorRegistry } from './extraction/external-extractor.js';
10
10
  export { ChangeTracker } from './core/change-tracker.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@customize-agent/knowledge",
3
- "version": "3.0.3",
3
+ "version": "3.0.4",
4
4
  "description": "Local knowledge base infrastructure for customize-agent",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -13,6 +13,9 @@
13
13
  }
14
14
  },
15
15
  "license": "MIT",
16
+ "engines": {
17
+ "node": ">=22.13.0"
18
+ },
16
19
  "author": "Pan-jijian",
17
20
  "repository": {
18
21
  "type": "git",