@customize-agent/knowledge 1.0.1
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.
- package/dist/chunking/text-chunker.d.ts +24 -0
- package/dist/chunking/text-chunker.js +90 -0
- package/dist/classification/classifier.d.ts +12 -0
- package/dist/classification/classifier.js +95 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.js +40 -0
- package/dist/core/change-tracker.d.ts +10 -0
- package/dist/core/change-tracker.js +66 -0
- package/dist/core/file-scanner.d.ts +8 -0
- package/dist/core/file-scanner.js +32 -0
- package/dist/core/index-state-store.d.ts +103 -0
- package/dist/core/index-state-store.js +439 -0
- package/dist/core/knowledge-base-manager.d.ts +76 -0
- package/dist/core/knowledge-base-manager.js +300 -0
- package/dist/core/multi-project-manager.d.ts +30 -0
- package/dist/core/multi-project-manager.js +163 -0
- package/dist/core/project-config.d.ts +9 -0
- package/dist/core/project-config.js +73 -0
- package/dist/core/project-id.d.ts +1 -0
- package/dist/core/project-id.js +8 -0
- package/dist/core/project-registry.d.ts +10 -0
- package/dist/core/project-registry.js +70 -0
- package/dist/dedup/dedup-engine.d.ts +20 -0
- package/dist/dedup/dedup-engine.js +78 -0
- package/dist/dedup/relationship-detector.d.ts +10 -0
- package/dist/dedup/relationship-detector.js +84 -0
- package/dist/embedding/embedding-provider.d.ts +15 -0
- package/dist/embedding/embedding-provider.js +31 -0
- package/dist/extraction/content-extractor.d.ts +36 -0
- package/dist/extraction/content-extractor.js +655 -0
- package/dist/extraction/external-extractor.d.ts +63 -0
- package/dist/extraction/external-extractor.js +139 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +22 -0
- package/dist/search/federation-search.d.ts +41 -0
- package/dist/search/federation-search.js +102 -0
- package/dist/server/dashboard-client.d.ts +2 -0
- package/dist/server/dashboard-client.js +396 -0
- package/dist/server/dashboard-i18n.d.ts +112 -0
- package/dist/server/dashboard-i18n.js +220 -0
- package/dist/server/dashboard-page.d.ts +6 -0
- package/dist/server/dashboard-page.js +138 -0
- package/dist/server/dashboard-server.d.ts +13 -0
- package/dist/server/dashboard-server.js +225 -0
- package/dist/server/dashboard-styles.d.ts +1 -0
- package/dist/server/dashboard-styles.js +152 -0
- package/dist/types.d.ts +82 -0
- package/dist/types.js +1 -0
- package/dist/vector/chroma-store.d.ts +39 -0
- package/dist/vector/chroma-store.js +131 -0
- package/dist/vector/collection-manager.d.ts +16 -0
- package/dist/vector/collection-manager.js +77 -0
- package/dist/vector/types.d.ts +33 -0
- package/dist/vector/types.js +1 -0
- package/dist/vector/vector-indexer.d.ts +18 -0
- package/dist/vector/vector-indexer.js +61 -0
- package/package.json +45 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
export class IndexStateStore {
|
|
5
|
+
db;
|
|
6
|
+
constructor(dbPath) {
|
|
7
|
+
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
8
|
+
this.db = new Database(dbPath);
|
|
9
|
+
this.db.pragma('journal_mode = WAL');
|
|
10
|
+
this.initTables();
|
|
11
|
+
}
|
|
12
|
+
loadActiveRecords() {
|
|
13
|
+
const rows = this.db.prepare(`
|
|
14
|
+
SELECT * FROM kb_index_state
|
|
15
|
+
WHERE status IN ('active', 'outdated', 'error')
|
|
16
|
+
`).all();
|
|
17
|
+
return new Map(rows.map(row => {
|
|
18
|
+
const record = this.rowToRecord(row);
|
|
19
|
+
return [record.relativePath, record];
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
upsertRecord(record) {
|
|
23
|
+
this.db.prepare(`
|
|
24
|
+
INSERT INTO kb_index_state (
|
|
25
|
+
relative_path, category, format, content_hash, file_size, mtime,
|
|
26
|
+
chunk_count, collection_name, indexed_at, last_verified_at,
|
|
27
|
+
status, error_message, metadata_json
|
|
28
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
29
|
+
ON CONFLICT(relative_path) DO UPDATE SET
|
|
30
|
+
category = excluded.category,
|
|
31
|
+
format = excluded.format,
|
|
32
|
+
content_hash = excluded.content_hash,
|
|
33
|
+
file_size = excluded.file_size,
|
|
34
|
+
mtime = excluded.mtime,
|
|
35
|
+
chunk_count = excluded.chunk_count,
|
|
36
|
+
collection_name = excluded.collection_name,
|
|
37
|
+
last_verified_at = excluded.last_verified_at,
|
|
38
|
+
status = excluded.status,
|
|
39
|
+
error_message = excluded.error_message,
|
|
40
|
+
metadata_json = excluded.metadata_json
|
|
41
|
+
`).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);
|
|
42
|
+
}
|
|
43
|
+
updateVerified(relativePath, mtime) {
|
|
44
|
+
this.db.prepare(`
|
|
45
|
+
UPDATE kb_index_state
|
|
46
|
+
SET mtime = ?, last_verified_at = ?, status = 'active'
|
|
47
|
+
WHERE relative_path = ?
|
|
48
|
+
`).run(Math.round(mtime), Date.now(), relativePath);
|
|
49
|
+
}
|
|
50
|
+
listRecords() {
|
|
51
|
+
const rows = this.db.prepare(`
|
|
52
|
+
SELECT * FROM kb_index_state
|
|
53
|
+
WHERE status = 'active'
|
|
54
|
+
ORDER BY category, relative_path
|
|
55
|
+
`).all();
|
|
56
|
+
return rows.map(row => this.rowToRecord(row));
|
|
57
|
+
}
|
|
58
|
+
replaceChunks(relativePath, chunks, file) {
|
|
59
|
+
const now = Date.now();
|
|
60
|
+
const transaction = this.db.transaction(() => {
|
|
61
|
+
this.db.prepare('DELETE FROM kb_chunks WHERE relative_path = ?').run(relativePath);
|
|
62
|
+
const insert = this.db.prepare(`
|
|
63
|
+
INSERT INTO kb_chunks (
|
|
64
|
+
id, relative_path, chunk_index, content, category, format,
|
|
65
|
+
collection_name, token_count, section_title, metadata_json, created_at
|
|
66
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
67
|
+
`);
|
|
68
|
+
for (const chunk of chunks) {
|
|
69
|
+
insert.run(`${relativePath}#${chunk.index}`, relativePath, chunk.index, chunk.text, file.category, file.format, file.collectionName, chunk.tokenCount, chunk.sectionTitle ?? null, JSON.stringify(chunk.metadata), now);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
transaction();
|
|
73
|
+
}
|
|
74
|
+
listChunks(options = {}) {
|
|
75
|
+
const conditions = [];
|
|
76
|
+
const params = [];
|
|
77
|
+
if (options.collectionName) {
|
|
78
|
+
conditions.push('collection_name = ?');
|
|
79
|
+
params.push(options.collectionName);
|
|
80
|
+
}
|
|
81
|
+
if (options.relativePath) {
|
|
82
|
+
conditions.push('relative_path = ?');
|
|
83
|
+
params.push(options.relativePath);
|
|
84
|
+
}
|
|
85
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
86
|
+
const limit = options.limit ? 'LIMIT ?' : '';
|
|
87
|
+
if (options.limit)
|
|
88
|
+
params.push(options.limit);
|
|
89
|
+
const rows = this.db.prepare(`
|
|
90
|
+
SELECT * FROM kb_chunks
|
|
91
|
+
${where}
|
|
92
|
+
ORDER BY relative_path, chunk_index
|
|
93
|
+
${limit}
|
|
94
|
+
`).all(...params);
|
|
95
|
+
return rows.map(row => this.rowToChunk(row, 0));
|
|
96
|
+
}
|
|
97
|
+
searchChunks(query, limit = 10) {
|
|
98
|
+
const terms = this.expandSearchTerms(query);
|
|
99
|
+
if (terms.length === 0)
|
|
100
|
+
return [];
|
|
101
|
+
const rows = this.db.prepare(`
|
|
102
|
+
SELECT * FROM kb_chunks
|
|
103
|
+
WHERE ${terms.map(() => '(LOWER(content) LIKE ? OR LOWER(relative_path) LIKE ? OR LOWER(category) LIKE ? OR LOWER(format) LIKE ?)').join(' OR ')}
|
|
104
|
+
ORDER BY created_at DESC
|
|
105
|
+
LIMIT ?
|
|
106
|
+
`).all(...terms.flatMap(term => [`%${term}%`, `%${term}%`, `%${term}%`, `%${term}%`]), limit * 6);
|
|
107
|
+
return rows
|
|
108
|
+
.map(row => this.rowToChunk(row, this.scoreChunk(`${String(row.relative_path)}\n${String(row.category)}\n${String(row.format)}\n${String(row.content)}`, terms)))
|
|
109
|
+
.filter(row => row.score > 0)
|
|
110
|
+
.sort((a, b) => b.score - a.score)
|
|
111
|
+
.slice(0, limit);
|
|
112
|
+
}
|
|
113
|
+
findExactDuplicate(contentHash, excludePath) {
|
|
114
|
+
const row = this.db.prepare(`
|
|
115
|
+
SELECT * FROM kb_file_hashes
|
|
116
|
+
WHERE content_hash = ? ${excludePath ? 'AND file_path != ?' : ''}
|
|
117
|
+
LIMIT 1
|
|
118
|
+
`).get(...(excludePath ? [contentHash, excludePath] : [contentHash]));
|
|
119
|
+
return row ? this.rowToFileHash(row) : undefined;
|
|
120
|
+
}
|
|
121
|
+
findNormalizedDuplicate(normalizedHash, excludePath) {
|
|
122
|
+
const row = this.db.prepare(`
|
|
123
|
+
SELECT * FROM kb_file_hashes
|
|
124
|
+
WHERE normalized_hash = ? ${excludePath ? 'AND file_path != ?' : ''}
|
|
125
|
+
LIMIT 1
|
|
126
|
+
`).get(...(excludePath ? [normalizedHash, excludePath] : [normalizedHash]));
|
|
127
|
+
return row ? this.rowToFileHash(row) : undefined;
|
|
128
|
+
}
|
|
129
|
+
upsertFileHash(record) {
|
|
130
|
+
const now = Date.now();
|
|
131
|
+
this.db.prepare(`
|
|
132
|
+
INSERT INTO kb_file_hashes (content_hash, file_path, file_size, category, normalized_hash, created_at, updated_at)
|
|
133
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
134
|
+
ON CONFLICT(file_path) DO UPDATE SET
|
|
135
|
+
content_hash = excluded.content_hash,
|
|
136
|
+
file_size = excluded.file_size,
|
|
137
|
+
category = excluded.category,
|
|
138
|
+
normalized_hash = excluded.normalized_hash,
|
|
139
|
+
updated_at = excluded.updated_at
|
|
140
|
+
`).run(record.contentHash, record.filePath, record.fileSize, record.category, record.normalizedHash ?? null, now, now);
|
|
141
|
+
}
|
|
142
|
+
upsertMinHash(record) {
|
|
143
|
+
this.db.prepare(`
|
|
144
|
+
INSERT INTO kb_minhash (file_path, signature, shingle_count, created_at)
|
|
145
|
+
VALUES (?, ?, ?, ?)
|
|
146
|
+
ON CONFLICT(file_path) DO UPDATE SET
|
|
147
|
+
signature = excluded.signature,
|
|
148
|
+
shingle_count = excluded.shingle_count,
|
|
149
|
+
created_at = excluded.created_at
|
|
150
|
+
`).run(record.filePath, Buffer.from(JSON.stringify(record.signature), 'utf8'), record.shingleCount, Date.now());
|
|
151
|
+
}
|
|
152
|
+
listMinHashes(excludePath) {
|
|
153
|
+
const rows = excludePath
|
|
154
|
+
? this.db.prepare('SELECT * FROM kb_minhash WHERE file_path != ?').all(excludePath)
|
|
155
|
+
: this.db.prepare('SELECT * FROM kb_minhash').all();
|
|
156
|
+
return rows.map(row => this.rowToMinHash(row));
|
|
157
|
+
}
|
|
158
|
+
addRelationship(relationship) {
|
|
159
|
+
this.db.prepare(`
|
|
160
|
+
INSERT INTO kb_relationships (
|
|
161
|
+
source_file, target_file, relationship_type, confidence, detail, user_confirmed, created_at
|
|
162
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
163
|
+
ON CONFLICT(source_file, target_file, relationship_type) DO UPDATE SET
|
|
164
|
+
confidence = excluded.confidence,
|
|
165
|
+
detail = excluded.detail,
|
|
166
|
+
user_confirmed = excluded.user_confirmed
|
|
167
|
+
`).run(relationship.sourceFile, relationship.targetFile, relationship.relationshipType, relationship.confidence, relationship.detail ?? null, relationship.userConfirmed, Date.now());
|
|
168
|
+
}
|
|
169
|
+
listRelationships(filePath) {
|
|
170
|
+
const rows = filePath
|
|
171
|
+
? this.db.prepare('SELECT * FROM kb_relationships WHERE source_file = ? OR target_file = ? ORDER BY created_at DESC').all(filePath, filePath)
|
|
172
|
+
: this.db.prepare('SELECT * FROM kb_relationships ORDER BY created_at DESC').all();
|
|
173
|
+
return rows.map(row => this.rowToRelationship(row));
|
|
174
|
+
}
|
|
175
|
+
setTags(relativePath, tags) {
|
|
176
|
+
const now = Date.now();
|
|
177
|
+
const transaction = this.db.transaction(() => {
|
|
178
|
+
this.db.prepare('DELETE FROM kb_tags WHERE file_path = ?').run(relativePath);
|
|
179
|
+
const insert = this.db.prepare('INSERT OR IGNORE INTO kb_tags (file_path, tag, created_at) VALUES (?, ?, ?)');
|
|
180
|
+
for (const tag of tags.map(tag => tag.trim()).filter(Boolean))
|
|
181
|
+
insert.run(relativePath, tag, now);
|
|
182
|
+
});
|
|
183
|
+
transaction();
|
|
184
|
+
}
|
|
185
|
+
listTags(relativePath) {
|
|
186
|
+
const rows = relativePath
|
|
187
|
+
? this.db.prepare('SELECT * FROM kb_tags WHERE file_path = ? ORDER BY tag').all(relativePath)
|
|
188
|
+
: this.db.prepare('SELECT * FROM kb_tags ORDER BY file_path, tag').all();
|
|
189
|
+
return rows.map(row => ({
|
|
190
|
+
filePath: String(row.file_path),
|
|
191
|
+
tag: String(row.tag),
|
|
192
|
+
createdAt: Number(row.created_at),
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
addIgnoreRule(pattern) {
|
|
196
|
+
this.db.prepare('INSERT OR IGNORE INTO kb_ignore_rules (pattern, enabled, created_at) VALUES (?, 1, ?)').run(pattern, Date.now());
|
|
197
|
+
}
|
|
198
|
+
listIgnoreRules() {
|
|
199
|
+
const rows = this.db.prepare('SELECT * FROM kb_ignore_rules ORDER BY created_at DESC').all();
|
|
200
|
+
return rows.map(row => ({
|
|
201
|
+
id: Number(row.id),
|
|
202
|
+
pattern: String(row.pattern),
|
|
203
|
+
enabled: Number(row.enabled) === 1,
|
|
204
|
+
createdAt: Number(row.created_at),
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
deleteRecord(relativePath) {
|
|
208
|
+
this.db.prepare('DELETE FROM kb_chunks WHERE relative_path = ?').run(relativePath);
|
|
209
|
+
this.db.prepare('DELETE FROM kb_index_state WHERE relative_path = ?').run(relativePath);
|
|
210
|
+
this.db.prepare('DELETE FROM kb_file_hashes WHERE file_path = ?').run(relativePath);
|
|
211
|
+
this.db.prepare('DELETE FROM kb_minhash WHERE file_path = ?').run(relativePath);
|
|
212
|
+
this.db.prepare('DELETE FROM kb_tags WHERE file_path = ?').run(relativePath);
|
|
213
|
+
this.db.prepare('DELETE FROM kb_relationships WHERE source_file = ? OR target_file = ?').run(relativePath, relativePath);
|
|
214
|
+
}
|
|
215
|
+
setMetadata(key, value) {
|
|
216
|
+
this.db.prepare(`
|
|
217
|
+
INSERT INTO kb_metadata (key, value) VALUES (?, ?)
|
|
218
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
219
|
+
`).run(key, value);
|
|
220
|
+
}
|
|
221
|
+
getStats() {
|
|
222
|
+
const stats = this.db.prepare(`
|
|
223
|
+
SELECT
|
|
224
|
+
COUNT(*) as file_count,
|
|
225
|
+
COALESCE(SUM(chunk_count), 0) as chunk_count,
|
|
226
|
+
COALESCE(SUM(file_size), 0) as total_size_bytes,
|
|
227
|
+
COALESCE(MAX(indexed_at), 0) as last_indexed_at
|
|
228
|
+
FROM kb_index_state
|
|
229
|
+
WHERE status = 'active'
|
|
230
|
+
`).get();
|
|
231
|
+
return {
|
|
232
|
+
fileCount: Number(stats.file_count ?? 0),
|
|
233
|
+
chunkCount: Number(stats.chunk_count ?? 0),
|
|
234
|
+
totalSizeBytes: Number(stats.total_size_bytes ?? 0),
|
|
235
|
+
lastIndexedAt: Number(stats.last_indexed_at ?? 0),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
listContentHashes() {
|
|
239
|
+
const rows = this.db.prepare(`
|
|
240
|
+
SELECT content_hash, relative_path FROM kb_index_state WHERE status = 'active'
|
|
241
|
+
`).all();
|
|
242
|
+
return rows.map(row => ({ contentHash: String(row.content_hash), relativePath: String(row.relative_path) }));
|
|
243
|
+
}
|
|
244
|
+
close() {
|
|
245
|
+
this.db.close();
|
|
246
|
+
}
|
|
247
|
+
initTables() {
|
|
248
|
+
this.db.exec(`
|
|
249
|
+
CREATE TABLE IF NOT EXISTS kb_index_state (
|
|
250
|
+
relative_path TEXT PRIMARY KEY,
|
|
251
|
+
category TEXT NOT NULL,
|
|
252
|
+
format TEXT NOT NULL,
|
|
253
|
+
content_hash TEXT NOT NULL,
|
|
254
|
+
file_size INTEGER NOT NULL,
|
|
255
|
+
mtime INTEGER NOT NULL,
|
|
256
|
+
chunk_count INTEGER NOT NULL DEFAULT 0,
|
|
257
|
+
collection_name TEXT NOT NULL,
|
|
258
|
+
indexed_at INTEGER NOT NULL,
|
|
259
|
+
last_verified_at INTEGER NOT NULL,
|
|
260
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
261
|
+
error_message TEXT,
|
|
262
|
+
metadata_json TEXT
|
|
263
|
+
);
|
|
264
|
+
CREATE INDEX IF NOT EXISTS idx_kb_state_status ON kb_index_state(status);
|
|
265
|
+
CREATE INDEX IF NOT EXISTS idx_kb_state_category ON kb_index_state(category);
|
|
266
|
+
CREATE INDEX IF NOT EXISTS idx_kb_state_collection ON kb_index_state(collection_name);
|
|
267
|
+
|
|
268
|
+
CREATE TABLE IF NOT EXISTS kb_chunks (
|
|
269
|
+
id TEXT PRIMARY KEY,
|
|
270
|
+
relative_path TEXT NOT NULL,
|
|
271
|
+
chunk_index INTEGER NOT NULL,
|
|
272
|
+
content TEXT NOT NULL,
|
|
273
|
+
category TEXT NOT NULL,
|
|
274
|
+
format TEXT NOT NULL,
|
|
275
|
+
collection_name TEXT NOT NULL,
|
|
276
|
+
token_count INTEGER NOT NULL,
|
|
277
|
+
section_title TEXT,
|
|
278
|
+
metadata_json TEXT,
|
|
279
|
+
created_at INTEGER NOT NULL
|
|
280
|
+
);
|
|
281
|
+
CREATE INDEX IF NOT EXISTS idx_kb_chunks_path ON kb_chunks(relative_path);
|
|
282
|
+
CREATE INDEX IF NOT EXISTS idx_kb_chunks_category ON kb_chunks(category);
|
|
283
|
+
CREATE INDEX IF NOT EXISTS idx_kb_chunks_collection ON kb_chunks(collection_name);
|
|
284
|
+
|
|
285
|
+
CREATE TABLE IF NOT EXISTS kb_file_hashes (
|
|
286
|
+
content_hash TEXT NOT NULL,
|
|
287
|
+
file_path TEXT PRIMARY KEY,
|
|
288
|
+
file_size INTEGER NOT NULL,
|
|
289
|
+
category TEXT NOT NULL,
|
|
290
|
+
normalized_hash TEXT,
|
|
291
|
+
created_at INTEGER NOT NULL,
|
|
292
|
+
updated_at INTEGER NOT NULL
|
|
293
|
+
);
|
|
294
|
+
CREATE INDEX IF NOT EXISTS idx_kb_hashes_content ON kb_file_hashes(content_hash);
|
|
295
|
+
CREATE INDEX IF NOT EXISTS idx_kb_hashes_norm ON kb_file_hashes(normalized_hash);
|
|
296
|
+
|
|
297
|
+
CREATE TABLE IF NOT EXISTS kb_minhash (
|
|
298
|
+
file_path TEXT PRIMARY KEY,
|
|
299
|
+
signature BLOB NOT NULL,
|
|
300
|
+
shingle_count INTEGER NOT NULL,
|
|
301
|
+
created_at INTEGER NOT NULL
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
CREATE TABLE IF NOT EXISTS kb_relationships (
|
|
305
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
306
|
+
source_file TEXT NOT NULL,
|
|
307
|
+
target_file TEXT NOT NULL,
|
|
308
|
+
relationship_type TEXT NOT NULL,
|
|
309
|
+
confidence REAL NOT NULL DEFAULT 1.0,
|
|
310
|
+
detail TEXT,
|
|
311
|
+
user_confirmed INTEGER NOT NULL DEFAULT 0,
|
|
312
|
+
created_at INTEGER NOT NULL,
|
|
313
|
+
UNIQUE(source_file, target_file, relationship_type)
|
|
314
|
+
);
|
|
315
|
+
CREATE INDEX IF NOT EXISTS idx_rel_source ON kb_relationships(source_file);
|
|
316
|
+
CREATE INDEX IF NOT EXISTS idx_rel_target ON kb_relationships(target_file);
|
|
317
|
+
CREATE INDEX IF NOT EXISTS idx_rel_type ON kb_relationships(relationship_type);
|
|
318
|
+
|
|
319
|
+
CREATE TABLE IF NOT EXISTS kb_tags (
|
|
320
|
+
file_path TEXT NOT NULL,
|
|
321
|
+
tag TEXT NOT NULL,
|
|
322
|
+
created_at INTEGER NOT NULL,
|
|
323
|
+
PRIMARY KEY (file_path, tag)
|
|
324
|
+
);
|
|
325
|
+
CREATE INDEX IF NOT EXISTS idx_tags_tag ON kb_tags(tag);
|
|
326
|
+
|
|
327
|
+
CREATE TABLE IF NOT EXISTS kb_metadata (
|
|
328
|
+
key TEXT PRIMARY KEY,
|
|
329
|
+
value TEXT NOT NULL
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
CREATE TABLE IF NOT EXISTS kb_ignore_rules (
|
|
333
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
334
|
+
pattern TEXT NOT NULL UNIQUE,
|
|
335
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
336
|
+
created_at INTEGER NOT NULL
|
|
337
|
+
);
|
|
338
|
+
`);
|
|
339
|
+
this.setMetadata('schema_version', '1');
|
|
340
|
+
}
|
|
341
|
+
rowToMinHash(row) {
|
|
342
|
+
const raw = row.signature;
|
|
343
|
+
const json = Buffer.isBuffer(raw) ? raw.toString('utf8') : String(raw);
|
|
344
|
+
return {
|
|
345
|
+
filePath: String(row.file_path),
|
|
346
|
+
signature: JSON.parse(json),
|
|
347
|
+
shingleCount: Number(row.shingle_count),
|
|
348
|
+
createdAt: Number(row.created_at),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
rowToFileHash(row) {
|
|
352
|
+
return {
|
|
353
|
+
contentHash: String(row.content_hash),
|
|
354
|
+
filePath: String(row.file_path),
|
|
355
|
+
fileSize: Number(row.file_size),
|
|
356
|
+
category: String(row.category),
|
|
357
|
+
normalizedHash: row.normalized_hash == null ? undefined : String(row.normalized_hash),
|
|
358
|
+
createdAt: Number(row.created_at),
|
|
359
|
+
updatedAt: Number(row.updated_at),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
rowToRelationship(row) {
|
|
363
|
+
return {
|
|
364
|
+
id: Number(row.id),
|
|
365
|
+
sourceFile: String(row.source_file),
|
|
366
|
+
targetFile: String(row.target_file),
|
|
367
|
+
relationshipType: String(row.relationship_type),
|
|
368
|
+
confidence: Number(row.confidence),
|
|
369
|
+
detail: row.detail == null ? undefined : String(row.detail),
|
|
370
|
+
userConfirmed: Number(row.user_confirmed),
|
|
371
|
+
createdAt: Number(row.created_at),
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
rowToChunk(row, score) {
|
|
375
|
+
return {
|
|
376
|
+
id: String(row.id),
|
|
377
|
+
relativePath: String(row.relative_path),
|
|
378
|
+
chunkIndex: Number(row.chunk_index),
|
|
379
|
+
content: String(row.content),
|
|
380
|
+
category: String(row.category),
|
|
381
|
+
format: String(row.format),
|
|
382
|
+
collectionName: String(row.collection_name),
|
|
383
|
+
tokenCount: Number(row.token_count),
|
|
384
|
+
sectionTitle: row.section_title == null ? undefined : String(row.section_title),
|
|
385
|
+
metadataJson: row.metadata_json == null ? undefined : String(row.metadata_json),
|
|
386
|
+
createdAt: Number(row.created_at),
|
|
387
|
+
score,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
expandSearchTerms(query) {
|
|
391
|
+
const normalized = query.toLowerCase().trim();
|
|
392
|
+
const terms = new Set(normalized.split(/[\s,,。;;::、]+/u).filter(Boolean));
|
|
393
|
+
if (normalized)
|
|
394
|
+
terms.add(normalized);
|
|
395
|
+
const synonyms = {
|
|
396
|
+
招标: ['招标', '投标', '标书', '招标文件', '投标文件', 'bid', 'tender', 'bidding'],
|
|
397
|
+
投标: ['招标', '投标', '标书', '招标文件', '投标文件', 'bid', 'tender', 'bidding'],
|
|
398
|
+
标书: ['招标', '投标', '标书', '招标文件', '投标文件', 'bid', 'tender', 'bidding'],
|
|
399
|
+
合同: ['合同', '协议', 'contract', 'agreement'],
|
|
400
|
+
pdf: ['pdf', 'document'],
|
|
401
|
+
文档: ['文档', '文件', 'document', 'pdf', 'office'],
|
|
402
|
+
};
|
|
403
|
+
for (const [key, values] of Object.entries(synonyms)) {
|
|
404
|
+
if (normalized.includes(key))
|
|
405
|
+
for (const value of values)
|
|
406
|
+
terms.add(value.toLowerCase());
|
|
407
|
+
}
|
|
408
|
+
return [...terms];
|
|
409
|
+
}
|
|
410
|
+
scoreChunk(content, terms) {
|
|
411
|
+
const lower = content.toLowerCase();
|
|
412
|
+
let score = 0;
|
|
413
|
+
for (const term of terms) {
|
|
414
|
+
let index = lower.indexOf(term);
|
|
415
|
+
while (index !== -1) {
|
|
416
|
+
score += 1;
|
|
417
|
+
index = lower.indexOf(term, index + term.length);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return score / Math.max(1, content.length / 1000);
|
|
421
|
+
}
|
|
422
|
+
rowToRecord(row) {
|
|
423
|
+
return {
|
|
424
|
+
relativePath: String(row.relative_path),
|
|
425
|
+
category: String(row.category),
|
|
426
|
+
format: String(row.format),
|
|
427
|
+
contentHash: String(row.content_hash),
|
|
428
|
+
fileSize: Number(row.file_size),
|
|
429
|
+
mtime: Number(row.mtime),
|
|
430
|
+
chunkCount: Number(row.chunk_count),
|
|
431
|
+
collectionName: String(row.collection_name),
|
|
432
|
+
indexedAt: Number(row.indexed_at),
|
|
433
|
+
lastVerifiedAt: Number(row.last_verified_at),
|
|
434
|
+
status: String(row.status),
|
|
435
|
+
errorMessage: row.error_message == null ? undefined : String(row.error_message),
|
|
436
|
+
metadataJson: row.metadata_json == null ? undefined : String(row.metadata_json),
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type EmbeddingProvider } from '../embedding/embedding-provider.js';
|
|
2
|
+
import type { ExternalExtractorRegistry } from '../extraction/external-extractor.js';
|
|
3
|
+
import { type FederatedResult, type SearchFilters } from '../search/federation-search.js';
|
|
4
|
+
import type { DiffResult, IndexStateRecord, KBScope, KnowledgeBaseStats, ProjectConfig } from '../types.js';
|
|
5
|
+
import type { VectorStoreInterface } from '../vector/types.js';
|
|
6
|
+
import { type VectorIndexResult } from '../vector/vector-indexer.js';
|
|
7
|
+
import { IndexStateStore, type ChunkSearchResult, type FileRelationship } from './index-state-store.js';
|
|
8
|
+
export interface KnowledgeBaseManagerOptions {
|
|
9
|
+
scope: Exclude<KBScope, 'session'>;
|
|
10
|
+
projectRoot?: string;
|
|
11
|
+
projectId?: string;
|
|
12
|
+
kbPath?: string;
|
|
13
|
+
storageRoot?: string;
|
|
14
|
+
embeddingProvider?: EmbeddingProvider;
|
|
15
|
+
vectorStores?: Map<string, VectorStoreInterface>;
|
|
16
|
+
externalExtractors?: ExternalExtractorRegistry;
|
|
17
|
+
}
|
|
18
|
+
export declare class KnowledgeBaseManager {
|
|
19
|
+
readonly scope: Exclude<KBScope, 'session'>;
|
|
20
|
+
readonly projectRoot?: string;
|
|
21
|
+
readonly projectId?: string;
|
|
22
|
+
readonly kbPath: string;
|
|
23
|
+
readonly store: IndexStateStore;
|
|
24
|
+
private readonly classifier;
|
|
25
|
+
private readonly scanner;
|
|
26
|
+
private readonly collections;
|
|
27
|
+
private readonly extractor;
|
|
28
|
+
private readonly chunker;
|
|
29
|
+
private readonly dedup;
|
|
30
|
+
private readonly relationshipDetector;
|
|
31
|
+
private readonly embeddingProvider;
|
|
32
|
+
private readonly vectorStores;
|
|
33
|
+
private readonly configManager;
|
|
34
|
+
private projectConfig?;
|
|
35
|
+
private lastSkippedFiles;
|
|
36
|
+
constructor(options: KnowledgeBaseManagerOptions);
|
|
37
|
+
initialize(): void;
|
|
38
|
+
incrementalIndex(): Promise<DiffResult>;
|
|
39
|
+
search(query: string, limit?: number): ChunkSearchResult[];
|
|
40
|
+
semanticSearch(query: string, options?: {
|
|
41
|
+
limit?: number;
|
|
42
|
+
filters?: SearchFilters;
|
|
43
|
+
collections?: string[];
|
|
44
|
+
}): Promise<FederatedResult>;
|
|
45
|
+
listRelationships(filePath?: string): FileRelationship[];
|
|
46
|
+
listFiles(): IndexStateRecord[];
|
|
47
|
+
addFile(sourcePath: string, targetRelativePath?: string): Promise<DiffResult>;
|
|
48
|
+
uploadFile(fileName: string, content: Buffer, targetRelativePath?: string): Promise<DiffResult>;
|
|
49
|
+
listFailedFiles(): DiffResult['skippedFiles'];
|
|
50
|
+
removeFile(relativePath: string): Promise<void>;
|
|
51
|
+
tagFile(relativePath: string, tags: string[]): void;
|
|
52
|
+
listTags(relativePath?: string): Array<{
|
|
53
|
+
filePath: string;
|
|
54
|
+
tag: string;
|
|
55
|
+
createdAt: number;
|
|
56
|
+
}>;
|
|
57
|
+
addIgnoreRule(pattern: string): void;
|
|
58
|
+
listIgnoreRules(): Array<{
|
|
59
|
+
id: number;
|
|
60
|
+
pattern: string;
|
|
61
|
+
enabled: boolean;
|
|
62
|
+
createdAt: number;
|
|
63
|
+
}>;
|
|
64
|
+
indexVectors(options?: {
|
|
65
|
+
collectionName?: string;
|
|
66
|
+
relativePath?: string;
|
|
67
|
+
limit?: number;
|
|
68
|
+
}): Promise<VectorIndexResult[]>;
|
|
69
|
+
getProjectConfig(): ProjectConfig | undefined;
|
|
70
|
+
getStats(): KnowledgeBaseStats;
|
|
71
|
+
close(): void;
|
|
72
|
+
private hasUsableContent;
|
|
73
|
+
private defaultUploadRelativePath;
|
|
74
|
+
private resolveKbRelativePath;
|
|
75
|
+
private normalizeRelativePath;
|
|
76
|
+
}
|