@chiway/contextweaver 1.1.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -28
- package/dist/{SearchService-MYPOCM3B.js → SearchService-WVD6THR3.js} +170 -82
- package/dist/chunk-3BNHQV5W.js +373 -0
- package/dist/chunk-BFCIZ52F.js +102 -0
- package/dist/{chunk-NQR4CGQ6.js → chunk-GDVB6PJ4.js} +58 -10
- package/dist/{lock-DVY3KJSK.js → chunk-HHYPQA3X.js} +2 -3
- package/dist/chunk-ISVCQFB4.js +223 -0
- package/dist/chunk-IZ6IUHNN.js +77 -0
- package/dist/{chunk-AMQQK4P7.js → chunk-JVKVSTQ3.js} +1 -2
- package/dist/chunk-LB42CZEB.js +18 -0
- package/dist/{chunk-6Z4JEEVJ.js → chunk-PPLFJGO3.js} +303 -58
- package/dist/chunk-R6CNZXZ7.js +143 -0
- package/dist/{chunk-RJURH22T.js → chunk-SKBAE26T.js} +0 -1
- package/dist/chunk-TPM6YP43.js +38 -0
- package/dist/{chunk-7G5V7YT5.js → chunk-V3K4YVAR.js} +12 -120
- package/dist/chunk-VWBKZ6QL.js +115 -0
- package/dist/chunk-XFIM2T6S.js +57 -0
- package/dist/{chunk-6QMYML5V.js → chunk-XMZZZKG7.js} +361 -295
- package/dist/chunk-XTWNT7KP.js +156 -0
- package/dist/chunk-Y6H7C3NA.js +85 -0
- package/dist/codebaseRetrieval-DIS5RH2C.js +14 -0
- package/dist/{config-BWZ6CU3W.js → config-LCOJHTCF.js} +1 -2
- package/dist/db-GBCLP4GG.js +68 -0
- package/dist/findReferences-N7ML7TUP.js +16 -0
- package/dist/getSymbolDefinition-6KMY4H33.js +17 -0
- package/dist/index.js +271 -40
- package/dist/listFiles-4VT2TPJD.js +14 -0
- package/dist/loadConfig-XTVT2OWW.js +9 -0
- package/dist/lock-HNKQ6X5B.js +8 -0
- package/dist/scanner-QDFZJLP7.js +13 -0
- package/dist/server-UAI3U7AB.js +347 -0
- package/dist/stats-AGKUCJQI.js +12 -0
- package/dist/vectorStore-4ODCERRO.js +12 -0
- package/package.json +9 -23
- package/dist/codebaseRetrieval-NLAMGOA2.js +0 -12
- package/dist/scanner-RFG4YWYI.js +0 -11
- package/dist/server-27HI7WZO.js +0 -147
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isDebugEnabled,
|
|
3
3
|
logger
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-JVKVSTQ3.js";
|
|
5
|
+
|
|
6
|
+
// src/db/index.ts
|
|
7
|
+
import crypto from "crypto";
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import os from "os";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import Database from "better-sqlite3";
|
|
5
12
|
|
|
6
13
|
// src/search/fts.ts
|
|
7
14
|
var tokenizerCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -26,7 +33,7 @@ function detectFtsTokenizer(db) {
|
|
|
26
33
|
function initFilesFts(db) {
|
|
27
34
|
const tokenizer = detectFtsTokenizer(db);
|
|
28
35
|
const tableExists = db.prepare(`
|
|
29
|
-
SELECT name FROM sqlite_master
|
|
36
|
+
SELECT name FROM sqlite_master
|
|
30
37
|
WHERE type='table' AND name='files_fts'
|
|
31
38
|
`).get();
|
|
32
39
|
if (!tableExists) {
|
|
@@ -34,25 +41,40 @@ function initFilesFts(db) {
|
|
|
34
41
|
CREATE VIRTUAL TABLE files_fts USING fts5(
|
|
35
42
|
path,
|
|
36
43
|
content,
|
|
44
|
+
content='files',
|
|
45
|
+
content_rowid='rowid',
|
|
37
46
|
tokenize='${tokenizer}'
|
|
38
47
|
);
|
|
39
48
|
`);
|
|
40
|
-
logger.info(`\u521B\u5EFA files_fts \u8868\uFF0Ctokenizer=${tokenizer}`);
|
|
41
|
-
syncFilesFts(db);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
function syncFilesFts(db) {
|
|
45
|
-
const fileCount = db.prepare("SELECT COUNT(*) as c FROM files WHERE content IS NOT NULL").get().c;
|
|
46
|
-
const ftsCount = db.prepare("SELECT COUNT(*) as c FROM files_fts").get().c;
|
|
47
|
-
if (ftsCount < fileCount) {
|
|
48
|
-
logger.info(`\u540C\u6B65 FTS \u7D22\u5F15: files=${fileCount}, fts=${ftsCount}`);
|
|
49
|
-
db.exec(`
|
|
50
|
-
DELETE FROM files_fts;
|
|
51
|
-
INSERT INTO files_fts(path, content)
|
|
52
|
-
SELECT path, content FROM files WHERE content IS NOT NULL;
|
|
53
|
-
`);
|
|
54
|
-
logger.info(`FTS \u7D22\u5F15\u540C\u6B65\u5B8C\u6210: ${fileCount} \u6761\u8BB0\u5F55`);
|
|
49
|
+
logger.info(`\u521B\u5EFA files_fts \u8868\uFF08\u5916\u90E8\u5185\u5BB9\u8868\uFF09\uFF0Ctokenizer=${tokenizer}`);
|
|
55
50
|
}
|
|
51
|
+
db.exec(`
|
|
52
|
+
CREATE TRIGGER IF NOT EXISTS files_ai
|
|
53
|
+
AFTER INSERT ON files
|
|
54
|
+
WHEN new.content IS NOT NULL
|
|
55
|
+
BEGIN
|
|
56
|
+
INSERT INTO files_fts(rowid, path, content)
|
|
57
|
+
VALUES (new.rowid, new.path, new.content);
|
|
58
|
+
END;
|
|
59
|
+
|
|
60
|
+
CREATE TRIGGER IF NOT EXISTS files_ad
|
|
61
|
+
AFTER DELETE ON files
|
|
62
|
+
WHEN old.content IS NOT NULL
|
|
63
|
+
BEGIN
|
|
64
|
+
INSERT INTO files_fts(files_fts, rowid, path, content)
|
|
65
|
+
VALUES('delete', old.rowid, old.path, old.content);
|
|
66
|
+
END;
|
|
67
|
+
|
|
68
|
+
CREATE TRIGGER IF NOT EXISTS files_au
|
|
69
|
+
AFTER UPDATE ON files
|
|
70
|
+
WHEN old.content IS NOT NULL OR new.content IS NOT NULL
|
|
71
|
+
BEGIN
|
|
72
|
+
INSERT INTO files_fts(files_fts, rowid, path, content)
|
|
73
|
+
SELECT 'delete', old.rowid, old.path, old.content WHERE old.content IS NOT NULL;
|
|
74
|
+
INSERT INTO files_fts(rowid, path, content)
|
|
75
|
+
SELECT new.rowid, new.path, new.content WHERE new.content IS NOT NULL;
|
|
76
|
+
END;
|
|
77
|
+
`);
|
|
56
78
|
}
|
|
57
79
|
function initChunksFts(db) {
|
|
58
80
|
const tokenizer = detectFtsTokenizer(db);
|
|
@@ -82,13 +104,17 @@ function isChunksFtsInitialized(db) {
|
|
|
82
104
|
return !!result;
|
|
83
105
|
}
|
|
84
106
|
function batchUpsertChunkFts(db, chunks) {
|
|
85
|
-
|
|
107
|
+
if (chunks.length === 0) return;
|
|
108
|
+
const paths = Array.from(new Set(chunks.map((c) => c.filePath)));
|
|
109
|
+
const deleteByPath = db.prepare("DELETE FROM chunks_fts WHERE file_path = ?");
|
|
86
110
|
const insertStmt = db.prepare(
|
|
87
111
|
"INSERT INTO chunks_fts(chunk_id, file_path, chunk_index, breadcrumb, content) VALUES (?, ?, ?, ?, ?)"
|
|
88
112
|
);
|
|
89
113
|
const transaction = db.transaction((items) => {
|
|
114
|
+
for (const p of paths) {
|
|
115
|
+
deleteByPath.run(p);
|
|
116
|
+
}
|
|
90
117
|
for (const item of items) {
|
|
91
|
-
deleteStmt.run(item.chunkId);
|
|
92
118
|
insertStmt.run(item.chunkId, item.filePath, item.chunkIndex, item.breadcrumb, item.content);
|
|
93
119
|
}
|
|
94
120
|
});
|
|
@@ -172,26 +198,6 @@ function searchChunksFts(db, query, limit) {
|
|
|
172
198
|
}
|
|
173
199
|
return results.sort((a, b) => b.score - a.score);
|
|
174
200
|
}
|
|
175
|
-
function batchUpsertFileFts(db, files) {
|
|
176
|
-
const deleteFts = db.prepare("DELETE FROM files_fts WHERE path = ?");
|
|
177
|
-
const insertFts = db.prepare("INSERT INTO files_fts(path, content) VALUES (?, ?)");
|
|
178
|
-
const transaction = db.transaction((items) => {
|
|
179
|
-
for (const item of items) {
|
|
180
|
-
deleteFts.run(item.path);
|
|
181
|
-
insertFts.run(item.path, item.content);
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
transaction(files);
|
|
185
|
-
}
|
|
186
|
-
function batchDeleteFileFts(db, paths) {
|
|
187
|
-
const stmt = db.prepare("DELETE FROM files_fts WHERE path = ?");
|
|
188
|
-
const transaction = db.transaction((items) => {
|
|
189
|
-
for (const path2 of items) {
|
|
190
|
-
stmt.run(path2);
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
transaction(paths);
|
|
194
|
-
}
|
|
195
201
|
function sanitizeQuery(query) {
|
|
196
202
|
return query.replace(/[():"*^./\\:@#$%&=+[\]{}<>|~`!?,;]/g, " ").replace(/\b(AND|OR|NOT|NEAR)\b/gi, " ").replace(/\s+/g, " ").trim();
|
|
197
203
|
}
|
|
@@ -342,11 +348,6 @@ function isFtsInitialized(db) {
|
|
|
342
348
|
}
|
|
343
349
|
|
|
344
350
|
// src/db/index.ts
|
|
345
|
-
import crypto from "crypto";
|
|
346
|
-
import fs from "fs";
|
|
347
|
-
import os from "os";
|
|
348
|
-
import path from "path";
|
|
349
|
-
import Database from "better-sqlite3";
|
|
350
351
|
var BASE_DIR = path.join(os.homedir(), ".contextweaver");
|
|
351
352
|
function getDirectoryBirthtime(projectPath) {
|
|
352
353
|
const gitDir = path.join(projectPath, ".git");
|
|
@@ -405,6 +406,7 @@ function initDb(projectId) {
|
|
|
405
406
|
value TEXT NOT NULL
|
|
406
407
|
)
|
|
407
408
|
`);
|
|
409
|
+
migrateSchema(db);
|
|
408
410
|
initFilesFts(db);
|
|
409
411
|
initChunksFts(db);
|
|
410
412
|
db.pragma("synchronous = NORMAL");
|
|
@@ -412,6 +414,148 @@ function initDb(projectId) {
|
|
|
412
414
|
db.pragma("cache_size = -64000");
|
|
413
415
|
return db;
|
|
414
416
|
}
|
|
417
|
+
var CURRENT_SCHEMA_VERSION = 3;
|
|
418
|
+
var METADATA_KEY_SCHEMA_VERSION = "schema_version";
|
|
419
|
+
function getSchemaVersion(db) {
|
|
420
|
+
const row = db.prepare("SELECT value FROM metadata WHERE key = ?").get(METADATA_KEY_SCHEMA_VERSION);
|
|
421
|
+
if (!row) return null;
|
|
422
|
+
const parsed = parseInt(row.value, 10);
|
|
423
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
424
|
+
}
|
|
425
|
+
function setSchemaVersion(db, version) {
|
|
426
|
+
db.prepare(`
|
|
427
|
+
INSERT INTO metadata (key, value)
|
|
428
|
+
VALUES (?, ?)
|
|
429
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
430
|
+
`).run(METADATA_KEY_SCHEMA_VERSION, String(version));
|
|
431
|
+
}
|
|
432
|
+
function isOldFilesFtsSchema(db) {
|
|
433
|
+
const row = db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='files_fts'`).get();
|
|
434
|
+
if (!row?.sql) return false;
|
|
435
|
+
return !row.sql.includes("content='files'");
|
|
436
|
+
}
|
|
437
|
+
function migrateSchema(db) {
|
|
438
|
+
const backupExists = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='files_fts_v1_backup'`).get();
|
|
439
|
+
const currentFtsExists = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='files_fts'`).get();
|
|
440
|
+
if (backupExists && currentFtsExists && !isOldFilesFtsSchema(db)) {
|
|
441
|
+
logger.warn("\u68C0\u6D4B\u5230\u6B8B\u7559\u5907\u4EFD\u8868 files_fts_v1_backup\uFF0C\u6E05\u7406\u4E2D");
|
|
442
|
+
db.exec("DROP TABLE files_fts_v1_backup");
|
|
443
|
+
}
|
|
444
|
+
const current = getSchemaVersion(db);
|
|
445
|
+
if (current === null) {
|
|
446
|
+
const fileCount = db.prepare("SELECT COUNT(*) as c FROM files").get().c;
|
|
447
|
+
const ftsExists = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='files_fts'`).get();
|
|
448
|
+
if (fileCount === 0 && !ftsExists) {
|
|
449
|
+
migrateToV3(db);
|
|
450
|
+
setSchemaVersion(db, CURRENT_SCHEMA_VERSION);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if ((current ?? 1) < 2) {
|
|
455
|
+
migrateToV2(db);
|
|
456
|
+
setSchemaVersion(db, 2);
|
|
457
|
+
}
|
|
458
|
+
if ((current ?? 2) < 3) {
|
|
459
|
+
migrateToV3(db);
|
|
460
|
+
setSchemaVersion(db, 3);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
function migrateToV2(db) {
|
|
464
|
+
const ftsExists = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='files_fts'`).get();
|
|
465
|
+
if (!ftsExists) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
if (!isOldFilesFtsSchema(db)) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
logger.info("\u6267\u884C schema \u8FC1\u79FB v1 \u2192 v2: files_fts \u8F6C\u4E3A\u5916\u90E8\u5185\u5BB9\u8868");
|
|
472
|
+
db.exec("DROP TABLE files_fts");
|
|
473
|
+
let tokenizer;
|
|
474
|
+
try {
|
|
475
|
+
db.exec(
|
|
476
|
+
`CREATE VIRTUAL TABLE IF NOT EXISTS _fts_probe USING fts5(content, tokenize='trigram');
|
|
477
|
+
DROP TABLE IF EXISTS _fts_probe;`
|
|
478
|
+
);
|
|
479
|
+
tokenizer = "trigram";
|
|
480
|
+
} catch {
|
|
481
|
+
tokenizer = "unicode61";
|
|
482
|
+
}
|
|
483
|
+
db.exec(`
|
|
484
|
+
CREATE VIRTUAL TABLE files_fts USING fts5(
|
|
485
|
+
path,
|
|
486
|
+
content,
|
|
487
|
+
content='files',
|
|
488
|
+
content_rowid='rowid',
|
|
489
|
+
tokenize='${tokenizer}'
|
|
490
|
+
);
|
|
491
|
+
`);
|
|
492
|
+
db.exec(`INSERT INTO files_fts(files_fts) VALUES('rebuild')`);
|
|
493
|
+
logger.info("schema \u8FC1\u79FB v1 \u2192 v2 \u5B8C\u6210");
|
|
494
|
+
}
|
|
495
|
+
function migrateToV3(db) {
|
|
496
|
+
db.exec(`
|
|
497
|
+
CREATE TABLE IF NOT EXISTS pending_marks (
|
|
498
|
+
path TEXT PRIMARY KEY,
|
|
499
|
+
hash TEXT NOT NULL,
|
|
500
|
+
created_at INTEGER NOT NULL
|
|
501
|
+
);
|
|
502
|
+
`);
|
|
503
|
+
logger.info("schema \u8FC1\u79FB v2 \u2192 v3 \u5B8C\u6210: pending_marks \u8868\u5DF2\u521B\u5EFA");
|
|
504
|
+
}
|
|
505
|
+
function insertPendingMarks(db, items) {
|
|
506
|
+
if (items.length === 0) return;
|
|
507
|
+
const now = Date.now();
|
|
508
|
+
const insert = db.prepare(`
|
|
509
|
+
INSERT INTO pending_marks (path, hash, created_at)
|
|
510
|
+
VALUES (?, ?, ?)
|
|
511
|
+
ON CONFLICT(path) DO UPDATE SET hash = excluded.hash, created_at = excluded.created_at
|
|
512
|
+
`);
|
|
513
|
+
const tx = db.transaction((data) => {
|
|
514
|
+
for (const it of data) {
|
|
515
|
+
insert.run(it.path, it.hash, now);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
tx(items);
|
|
519
|
+
}
|
|
520
|
+
function deletePendingMarks(db, paths) {
|
|
521
|
+
if (paths.length === 0) return;
|
|
522
|
+
const del = db.prepare("DELETE FROM pending_marks WHERE path = ?");
|
|
523
|
+
const tx = db.transaction((items) => {
|
|
524
|
+
for (const p of items) del.run(p);
|
|
525
|
+
});
|
|
526
|
+
tx(paths);
|
|
527
|
+
}
|
|
528
|
+
function replayPendingMarks(db) {
|
|
529
|
+
const rows = db.prepare("SELECT path, hash FROM pending_marks").all();
|
|
530
|
+
if (rows.length === 0) return { applied: 0, discarded: 0 };
|
|
531
|
+
const update = db.prepare(`
|
|
532
|
+
UPDATE files SET vector_index_hash = ?
|
|
533
|
+
WHERE path = ? AND hash = ?
|
|
534
|
+
`);
|
|
535
|
+
const del = db.prepare("DELETE FROM pending_marks WHERE path = ?");
|
|
536
|
+
let applied = 0;
|
|
537
|
+
let discarded = 0;
|
|
538
|
+
const tx = db.transaction(() => {
|
|
539
|
+
for (const r of rows) {
|
|
540
|
+
const info = update.run(r.hash, r.path, r.hash);
|
|
541
|
+
if (info.changes > 0) {
|
|
542
|
+
applied++;
|
|
543
|
+
} else {
|
|
544
|
+
discarded++;
|
|
545
|
+
}
|
|
546
|
+
del.run(r.path);
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
tx();
|
|
550
|
+
if (applied > 0 || discarded > 0) {
|
|
551
|
+
logger.info({ applied, discarded }, "pending_marks \u91CD\u653E\u5B8C\u6210");
|
|
552
|
+
}
|
|
553
|
+
return { applied, discarded };
|
|
554
|
+
}
|
|
555
|
+
function countPendingMarks(db) {
|
|
556
|
+
const row = db.prepare("SELECT COUNT(*) as c FROM pending_marks").get();
|
|
557
|
+
return row.c;
|
|
558
|
+
}
|
|
415
559
|
function closeDb(db) {
|
|
416
560
|
db.close();
|
|
417
561
|
}
|
|
@@ -467,15 +611,6 @@ function batchUpsert(db, files) {
|
|
|
467
611
|
}
|
|
468
612
|
});
|
|
469
613
|
transaction(files);
|
|
470
|
-
const ftsFiles = [];
|
|
471
|
-
for (const f of files) {
|
|
472
|
-
if (f.content !== null) {
|
|
473
|
-
ftsFiles.push({ path: f.path, content: f.content });
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
if (ftsFiles.length > 0) {
|
|
477
|
-
batchUpsertFileFts(db, ftsFiles);
|
|
478
|
-
}
|
|
479
614
|
}
|
|
480
615
|
function batchUpdateMtime(db, items) {
|
|
481
616
|
const update = db.prepare("UPDATE files SET mtime = ? WHERE path = ?");
|
|
@@ -498,9 +633,6 @@ function batchDelete(db, paths) {
|
|
|
498
633
|
}
|
|
499
634
|
});
|
|
500
635
|
transaction(paths);
|
|
501
|
-
if (paths.length > 0) {
|
|
502
|
-
batchDeleteFileFts(db, paths);
|
|
503
|
-
}
|
|
504
636
|
}
|
|
505
637
|
function clear(db) {
|
|
506
638
|
db.exec("DELETE FROM files");
|
|
@@ -508,6 +640,10 @@ function clear(db) {
|
|
|
508
640
|
db.exec("DELETE FROM chunks_fts");
|
|
509
641
|
}
|
|
510
642
|
var METADATA_KEY_EMBEDDING_DIMENSIONS = "embedding_dimensions";
|
|
643
|
+
var METADATA_KEY_INDEX_VERSION = "index_version";
|
|
644
|
+
var METADATA_KEY_LANCEDB_MIGRATION_STATE = "lancedb_migration_displaycode_state";
|
|
645
|
+
var METADATA_KEY_LANCEDB_MIGRATION_LOCK = "lancedb_migration_lock";
|
|
646
|
+
var MIGRATION_LOCK_STALE_MS = 10 * 60 * 1e3;
|
|
511
647
|
function getMetadata(db, key) {
|
|
512
648
|
const row = db.prepare("SELECT value FROM metadata WHERE key = ?").get(key);
|
|
513
649
|
return row?.value ?? null;
|
|
@@ -528,6 +664,99 @@ function getStoredEmbeddingDimensions(db) {
|
|
|
528
664
|
function setStoredEmbeddingDimensions(db, dimensions) {
|
|
529
665
|
setMetadata(db, METADATA_KEY_EMBEDDING_DIMENSIONS, String(dimensions));
|
|
530
666
|
}
|
|
667
|
+
function incrementStat(db, key, by = 1) {
|
|
668
|
+
const delta = Math.trunc(by);
|
|
669
|
+
db.prepare(`
|
|
670
|
+
INSERT INTO metadata (key, value)
|
|
671
|
+
VALUES (?, ?)
|
|
672
|
+
ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(value AS INTEGER) + CAST(? AS INTEGER) AS TEXT)
|
|
673
|
+
`).run(key, String(delta), delta);
|
|
674
|
+
}
|
|
675
|
+
function setStatJson(db, key, value) {
|
|
676
|
+
setMetadata(db, key, JSON.stringify(value));
|
|
677
|
+
}
|
|
678
|
+
function getStatJson(db, key) {
|
|
679
|
+
const raw = getMetadata(db, key);
|
|
680
|
+
if (raw === null) return null;
|
|
681
|
+
try {
|
|
682
|
+
return JSON.parse(raw);
|
|
683
|
+
} catch {
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
function getAllStats(db) {
|
|
688
|
+
const rows = db.prepare(`SELECT key, value FROM metadata WHERE key LIKE 'stats.%'`).all();
|
|
689
|
+
const out = {};
|
|
690
|
+
for (const row of rows) out[row.key] = row.value;
|
|
691
|
+
return out;
|
|
692
|
+
}
|
|
693
|
+
function collectHealthSnapshot(db) {
|
|
694
|
+
const agg = db.prepare("SELECT COUNT(*) as c, COALESCE(SUM(size), 0) as bytes FROM files").get();
|
|
695
|
+
const langRows = db.prepare("SELECT language, COUNT(*) as c FROM files GROUP BY language").all();
|
|
696
|
+
const byLanguage = {};
|
|
697
|
+
for (const row of langRows) byLanguage[row.language] = row.c;
|
|
698
|
+
return {
|
|
699
|
+
totalFiles: agg.c,
|
|
700
|
+
totalBytes: agg.bytes,
|
|
701
|
+
byLanguage,
|
|
702
|
+
pendingMarks: countPendingMarks(db),
|
|
703
|
+
migrationState: getLanceDbMigrationState(db),
|
|
704
|
+
embeddingDimensions: getStoredEmbeddingDimensions(db),
|
|
705
|
+
indexVersion: getIndexVersion(db)
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
function getIndexVersion(db) {
|
|
709
|
+
const value = getMetadata(db, METADATA_KEY_INDEX_VERSION);
|
|
710
|
+
if (value === null) return 0;
|
|
711
|
+
const parsed = parseInt(value, 10);
|
|
712
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
713
|
+
}
|
|
714
|
+
function incrementIndexVersion(db) {
|
|
715
|
+
const next = getIndexVersion(db) + 1;
|
|
716
|
+
setMetadata(db, METADATA_KEY_INDEX_VERSION, String(next));
|
|
717
|
+
return next;
|
|
718
|
+
}
|
|
719
|
+
function getLanceDbMigrationState(db) {
|
|
720
|
+
const value = getMetadata(db, METADATA_KEY_LANCEDB_MIGRATION_STATE);
|
|
721
|
+
if (value === "pending" || value === "done" || value === "aborted") return value;
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
function setLanceDbMigrationState(db, state) {
|
|
725
|
+
setMetadata(db, METADATA_KEY_LANCEDB_MIGRATION_STATE, state);
|
|
726
|
+
}
|
|
727
|
+
function clearAllVectorIndexHash(db) {
|
|
728
|
+
const info = db.prepare("UPDATE files SET vector_index_hash = NULL").run();
|
|
729
|
+
return info.changes;
|
|
730
|
+
}
|
|
731
|
+
function tryAcquireLanceDbMigrationLock(db) {
|
|
732
|
+
const now = Date.now();
|
|
733
|
+
const pid = process.pid;
|
|
734
|
+
const lockValue = JSON.stringify({ pid, acquiredAt: now });
|
|
735
|
+
const existing = getMetadata(db, METADATA_KEY_LANCEDB_MIGRATION_LOCK);
|
|
736
|
+
if (existing) {
|
|
737
|
+
try {
|
|
738
|
+
const parsed = JSON.parse(existing);
|
|
739
|
+
if (parsed.pid === pid) return true;
|
|
740
|
+
if (now - parsed.acquiredAt < MIGRATION_LOCK_STALE_MS) return false;
|
|
741
|
+
logger.warn(
|
|
742
|
+
{ stalePid: parsed.pid, age: now - parsed.acquiredAt },
|
|
743
|
+
"\u68C0\u6D4B\u5230\u50F5\u5C38\u8FC1\u79FB\u9501\uFF0C\u5F3A\u5236\u593A\u53D6"
|
|
744
|
+
);
|
|
745
|
+
} catch {
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
db.prepare(`
|
|
749
|
+
INSERT INTO metadata (key, value)
|
|
750
|
+
VALUES (?, ?)
|
|
751
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
752
|
+
`).run(METADATA_KEY_LANCEDB_MIGRATION_LOCK, lockValue);
|
|
753
|
+
const reread = getMetadata(db, METADATA_KEY_LANCEDB_MIGRATION_LOCK);
|
|
754
|
+
if (reread !== lockValue) return false;
|
|
755
|
+
return true;
|
|
756
|
+
}
|
|
757
|
+
function releaseLanceDbMigrationLock(db) {
|
|
758
|
+
db.prepare("DELETE FROM metadata WHERE key = ?").run(METADATA_KEY_LANCEDB_MIGRATION_LOCK);
|
|
759
|
+
}
|
|
531
760
|
|
|
532
761
|
export {
|
|
533
762
|
isChunksFtsInitialized,
|
|
@@ -539,6 +768,11 @@ export {
|
|
|
539
768
|
isFtsInitialized,
|
|
540
769
|
generateProjectId,
|
|
541
770
|
initDb,
|
|
771
|
+
migrateSchema,
|
|
772
|
+
insertPendingMarks,
|
|
773
|
+
deletePendingMarks,
|
|
774
|
+
replayPendingMarks,
|
|
775
|
+
countPendingMarks,
|
|
542
776
|
closeDb,
|
|
543
777
|
getAllFileMeta,
|
|
544
778
|
getFilesNeedingVectorIndex,
|
|
@@ -550,6 +784,17 @@ export {
|
|
|
550
784
|
batchDelete,
|
|
551
785
|
clear,
|
|
552
786
|
getStoredEmbeddingDimensions,
|
|
553
|
-
setStoredEmbeddingDimensions
|
|
787
|
+
setStoredEmbeddingDimensions,
|
|
788
|
+
incrementStat,
|
|
789
|
+
setStatJson,
|
|
790
|
+
getStatJson,
|
|
791
|
+
getAllStats,
|
|
792
|
+
collectHealthSnapshot,
|
|
793
|
+
getIndexVersion,
|
|
794
|
+
incrementIndexVersion,
|
|
795
|
+
getLanceDbMigrationState,
|
|
796
|
+
setLanceDbMigrationState,
|
|
797
|
+
clearAllVectorIndexHash,
|
|
798
|
+
tryAcquireLanceDbMigrationLock,
|
|
799
|
+
releaseLanceDbMigrationLock
|
|
554
800
|
};
|
|
555
|
-
//# sourceMappingURL=chunk-6Z4JEEVJ.js.map
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChunkContentLoader
|
|
3
|
+
} from "./chunk-XFIM2T6S.js";
|
|
4
|
+
import {
|
|
5
|
+
getVectorStore
|
|
6
|
+
} from "./chunk-3BNHQV5W.js";
|
|
7
|
+
import {
|
|
8
|
+
ensureIndexed,
|
|
9
|
+
formatTextResponse
|
|
10
|
+
} from "./chunk-VWBKZ6QL.js";
|
|
11
|
+
import {
|
|
12
|
+
generateProjectId,
|
|
13
|
+
initDb,
|
|
14
|
+
searchChunksFts
|
|
15
|
+
} from "./chunk-PPLFJGO3.js";
|
|
16
|
+
import {
|
|
17
|
+
logger
|
|
18
|
+
} from "./chunk-JVKVSTQ3.js";
|
|
19
|
+
|
|
20
|
+
// src/mcp/tools/findReferences.ts
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
var findReferencesSchema = z.object({
|
|
23
|
+
repo_path: z.string().describe(
|
|
24
|
+
"The absolute file system path to the repository root. (e.g., '/Users/dev/my-project')"
|
|
25
|
+
),
|
|
26
|
+
symbol: z.string().min(1).describe("The exact symbol name to search for."),
|
|
27
|
+
exclude_definition: z.boolean().optional().describe("Exclude chunks whose breadcrumb tail matches the symbol name."),
|
|
28
|
+
max_results: z.number().int().positive().max(200).optional().describe("Maximum number of references to return. Defaults to 50.")
|
|
29
|
+
});
|
|
30
|
+
function escapeRegex(text) {
|
|
31
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
32
|
+
}
|
|
33
|
+
function buildSymbolPattern(symbol) {
|
|
34
|
+
return new RegExp(`(?<![\\w$])${escapeRegex(symbol)}(?![\\w$])`, "u");
|
|
35
|
+
}
|
|
36
|
+
function breadcrumbTail(breadcrumb) {
|
|
37
|
+
const tail = breadcrumb.split(">").pop();
|
|
38
|
+
return tail?.trim() ?? "";
|
|
39
|
+
}
|
|
40
|
+
function countLinesBefore(content, index) {
|
|
41
|
+
let line = 1;
|
|
42
|
+
for (let i = 0; i < index && i < content.length; i++) {
|
|
43
|
+
if (content[i] === "\n") {
|
|
44
|
+
line += 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return line;
|
|
48
|
+
}
|
|
49
|
+
function locateMatches(code, pattern, baseLine) {
|
|
50
|
+
return code.split("\n").flatMap(
|
|
51
|
+
(text, offset) => pattern.test(text) ? [
|
|
52
|
+
{
|
|
53
|
+
line: baseLine + offset,
|
|
54
|
+
snippet: text.trim()
|
|
55
|
+
}
|
|
56
|
+
] : []
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
async function handleFindReferences(args, onProgress) {
|
|
60
|
+
const { repo_path, symbol, exclude_definition = false, max_results = 50 } = args;
|
|
61
|
+
const projectId = generateProjectId(repo_path);
|
|
62
|
+
logger.info(
|
|
63
|
+
{ repo_path, symbol, exclude_definition, max_results },
|
|
64
|
+
"MCP find-references \u8C03\u7528\u5F00\u59CB"
|
|
65
|
+
);
|
|
66
|
+
await ensureIndexed(repo_path, projectId, { onProgress });
|
|
67
|
+
const db = initDb(projectId);
|
|
68
|
+
try {
|
|
69
|
+
const hits = searchChunksFts(db, symbol, Math.max(max_results * 2, 20));
|
|
70
|
+
const uniquePaths = Array.from(new Set(hits.map((hit) => hit.filePath)));
|
|
71
|
+
const vectorStore = await getVectorStore(projectId);
|
|
72
|
+
const chunkMap = await vectorStore.getFilesChunks(uniquePaths);
|
|
73
|
+
const chunkByKey = /* @__PURE__ */ new Map();
|
|
74
|
+
for (const [filePath, chunks] of chunkMap) {
|
|
75
|
+
for (const chunk of chunks) {
|
|
76
|
+
chunkByKey.set(`${filePath}#${chunk.chunk_index}`, chunk);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const slices = Array.from(chunkByKey.values()).map((chunk) => ({
|
|
80
|
+
filePath: chunk.file_path,
|
|
81
|
+
start_index: chunk.start_index,
|
|
82
|
+
end_index: chunk.end_index
|
|
83
|
+
}));
|
|
84
|
+
const loader = new ChunkContentLoader(db);
|
|
85
|
+
const codeMap = loader.loadMany(slices);
|
|
86
|
+
const fileContentStmt = db.prepare("SELECT content FROM files WHERE path = ?");
|
|
87
|
+
const fullFileCache = /* @__PURE__ */ new Map();
|
|
88
|
+
const pattern = buildSymbolPattern(symbol);
|
|
89
|
+
const matches = [];
|
|
90
|
+
for (const hit of hits) {
|
|
91
|
+
if (matches.length >= max_results) {
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
const chunk = chunkByKey.get(`${hit.filePath}#${hit.chunkIndex}`);
|
|
95
|
+
if (!chunk) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (exclude_definition && breadcrumbTail(chunk.breadcrumb) === symbol) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const sliceKey = ChunkContentLoader.key({
|
|
102
|
+
filePath: chunk.file_path,
|
|
103
|
+
start_index: chunk.start_index,
|
|
104
|
+
end_index: chunk.end_index
|
|
105
|
+
});
|
|
106
|
+
const code = codeMap.get(sliceKey) ?? "";
|
|
107
|
+
if (!code) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
let fullContent = fullFileCache.get(chunk.file_path);
|
|
111
|
+
if (fullContent === void 0) {
|
|
112
|
+
const row = fileContentStmt.get(chunk.file_path);
|
|
113
|
+
fullContent = row?.content ?? "";
|
|
114
|
+
fullFileCache.set(chunk.file_path, fullContent);
|
|
115
|
+
}
|
|
116
|
+
const baseLine = countLinesBefore(fullContent, chunk.start_index);
|
|
117
|
+
for (const match of locateMatches(code, pattern, baseLine)) {
|
|
118
|
+
matches.push({
|
|
119
|
+
filePath: chunk.file_path,
|
|
120
|
+
line: match.line,
|
|
121
|
+
breadcrumb: chunk.breadcrumb,
|
|
122
|
+
snippet: match.snippet
|
|
123
|
+
});
|
|
124
|
+
if (matches.length >= max_results) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const body = matches.length > 0 ? matches.map(
|
|
130
|
+
(match) => `- ${match.filePath}:${match.line} | ${match.breadcrumb || "-"} | ${match.snippet}`
|
|
131
|
+
).join("\n") : "No exact text references found.";
|
|
132
|
+
return formatTextResponse(`Found ${matches.length} text references for "${symbol}"
|
|
133
|
+
|
|
134
|
+
${body}`);
|
|
135
|
+
} finally {
|
|
136
|
+
db.close();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export {
|
|
141
|
+
findReferencesSchema,
|
|
142
|
+
handleFindReferences
|
|
143
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_CONFIG
|
|
3
|
+
} from "./chunk-BFCIZ52F.js";
|
|
4
|
+
|
|
5
|
+
// src/defaultEnv.ts
|
|
6
|
+
function getDefaultEnvFileContent() {
|
|
7
|
+
return `# ContextWeaver \u793A\u4F8B\u73AF\u5883\u53D8\u91CF\u914D\u7F6E\u6587\u4EF6
|
|
8
|
+
|
|
9
|
+
# Embedding API \u914D\u7F6E\uFF08\u5FC5\u9700\uFF09
|
|
10
|
+
EMBEDDINGS_API_KEY=your-api-key-here
|
|
11
|
+
EMBEDDINGS_BASE_URL=https://api.siliconflow.cn/v1/embeddings
|
|
12
|
+
EMBEDDINGS_MODEL=BAAI/bge-m3
|
|
13
|
+
EMBEDDINGS_MAX_CONCURRENCY=10
|
|
14
|
+
EMBEDDINGS_DIMENSIONS=1024
|
|
15
|
+
|
|
16
|
+
# Reranker \u914D\u7F6E\uFF08\u5FC5\u9700\uFF09
|
|
17
|
+
RERANK_API_KEY=your-api-key-here
|
|
18
|
+
RERANK_BASE_URL=https://api.siliconflow.cn/v1/rerank
|
|
19
|
+
RERANK_MODEL=BAAI/bge-reranker-v2-m3
|
|
20
|
+
RERANK_TOP_N=20
|
|
21
|
+
|
|
22
|
+
# \u641C\u7D22\u53C2\u6570\uFF08\u53EF\u9009\uFF0C\u8986\u76D6\u5185\u7F6E\u9ED8\u8BA4\u503C\uFF09
|
|
23
|
+
CW_SEARCH_WVEC=${DEFAULT_CONFIG.wVec}
|
|
24
|
+
CW_SEARCH_WLEX=${DEFAULT_CONFIG.wLex}
|
|
25
|
+
CW_SEARCH_RERANK_TOP_N=${DEFAULT_CONFIG.rerankTopN}
|
|
26
|
+
CW_SEARCH_MAX_TOTAL_CHARS=${DEFAULT_CONFIG.maxTotalChars}
|
|
27
|
+
CW_SEARCH_VECTOR_TOP_K=${DEFAULT_CONFIG.vectorTopK}
|
|
28
|
+
CW_SEARCH_SMART_MAX_K=${DEFAULT_CONFIG.smartMaxK}
|
|
29
|
+
CW_SEARCH_IMPORT_FILES_PER_SEED=${DEFAULT_CONFIG.importFilesPerSeed}
|
|
30
|
+
|
|
31
|
+
# \u7D22\u5F15\u5FFD\u7565\u6A21\u5F0F\uFF08\u53EF\u9009\uFF0C\u9017\u53F7\u5206\u9694\uFF0C\u9ED8\u8BA4\u5DF2\u5305\u542B\u5E38\u89C1\u5FFD\u7565\u9879\uFF09
|
|
32
|
+
# IGNORE_PATTERNS=.venv,node_modules
|
|
33
|
+
`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
getDefaultEnvFileContent
|
|
38
|
+
};
|