@hsingjui/contextweaver 0.0.2 → 0.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.
- package/README.md +4 -0
- package/dist/{SearchService-NM6J3XHC.js → SearchService-DYGJT2DZ.js} +269 -241
- package/dist/chunk-C7XDGBT5.js +172 -0
- package/dist/{chunk-C7PBP6HL.js → chunk-O3HDM3CF.js} +103 -72
- package/dist/{chunk-JNMCEE3A.js → chunk-QWQ64TBE.js} +266 -275
- package/dist/{chunk-2HG4HNNX.js → chunk-SKBAE26T.js} +132 -28
- package/dist/{chunk-NHKBCYHN.js → chunk-VW5RACJC.js} +119 -222
- package/dist/{chunk-UWQSUTEL.js → chunk-WWYSLCNZ.js} +131 -45
- package/dist/{codebaseRetrieval-GPSIAFSZ.js → codebaseRetrieval-AV4GK6FT.js} +4 -3
- package/dist/{config-BVORFWIO.js → config-LCOJHTCF.js} +5 -5
- package/dist/index.js +47 -32
- package/dist/lock-PX2BX2YN.js +106 -0
- package/dist/scanner-HYP3L57R.js +10 -0
- package/dist/{server-Y4GFTGLG.js → server-7PYHHTOM.js} +7 -8
- package/package.json +5 -3
- package/dist/scanner-E52TXHEM.js +0 -9
|
@@ -3,18 +3,20 @@ import {
|
|
|
3
3
|
batchUpdateVectorIndexHash,
|
|
4
4
|
batchUpsertChunkFts,
|
|
5
5
|
clearVectorIndexHash,
|
|
6
|
-
isChunksFtsInitialized
|
|
6
|
+
isChunksFtsInitialized
|
|
7
|
+
} from "./chunk-VW5RACJC.js";
|
|
8
|
+
import {
|
|
7
9
|
logger
|
|
8
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-C7XDGBT5.js";
|
|
9
11
|
import {
|
|
10
12
|
getEmbeddingConfig
|
|
11
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-SKBAE26T.js";
|
|
12
14
|
|
|
13
15
|
// src/vectorStore/index.ts
|
|
14
|
-
import * as lancedb from "@lancedb/lancedb";
|
|
15
|
-
import path from "path";
|
|
16
|
-
import os from "os";
|
|
17
16
|
import fs from "fs";
|
|
17
|
+
import os from "os";
|
|
18
|
+
import path from "path";
|
|
19
|
+
import * as lancedb from "@lancedb/lancedb";
|
|
18
20
|
var BASE_DIR = path.join(os.homedir(), ".contextweaver");
|
|
19
21
|
var VectorStore = class {
|
|
20
22
|
db = null;
|
|
@@ -49,11 +51,14 @@ var VectorStore = class {
|
|
|
49
51
|
if (this.table) return;
|
|
50
52
|
if (!this.db) throw new Error("VectorStore not initialized");
|
|
51
53
|
if (records.length === 0) return;
|
|
52
|
-
this.table = await this.db.createTable(
|
|
54
|
+
this.table = await this.db.createTable(
|
|
55
|
+
"chunks",
|
|
56
|
+
records
|
|
57
|
+
);
|
|
53
58
|
}
|
|
54
59
|
/**
|
|
55
60
|
* 单调版本更新:先插入新版本,再删除旧版本
|
|
56
|
-
*
|
|
61
|
+
*
|
|
57
62
|
* 这保证了:
|
|
58
63
|
* - 最坏情况(崩溃)是新旧版本共存(不缺失)
|
|
59
64
|
* - 正常情况下旧版本被清理
|
|
@@ -70,18 +75,20 @@ var VectorStore = class {
|
|
|
70
75
|
await this.table.add(records);
|
|
71
76
|
}
|
|
72
77
|
if (this.table) {
|
|
73
|
-
await this.table.delete(
|
|
78
|
+
await this.table.delete(
|
|
79
|
+
`file_path = '${this.escapeString(filePath)}' AND file_hash != '${this.escapeString(newHash)}'`
|
|
80
|
+
);
|
|
74
81
|
}
|
|
75
82
|
}
|
|
76
83
|
/**
|
|
77
84
|
* 批量 upsert 多个文件(性能优化版,带分批机制)
|
|
78
|
-
*
|
|
85
|
+
*
|
|
79
86
|
* 流程:
|
|
80
87
|
* 1. 将文件分成小批次(每批最多 BATCH_FILES 个文件或 BATCH_RECORDS 条记录)
|
|
81
88
|
* 2. 每批执行:插入新 records → 删除旧版本
|
|
82
|
-
*
|
|
89
|
+
*
|
|
83
90
|
* 分批是必要的,因为 LanceDB native 模块在处理超大数据时可能崩溃
|
|
84
|
-
*
|
|
91
|
+
*
|
|
85
92
|
* @param files 文件列表,每个包含 path、hash 和 records
|
|
86
93
|
*/
|
|
87
94
|
async batchUpsertFiles(files) {
|
|
@@ -122,7 +129,9 @@ var VectorStore = class {
|
|
|
122
129
|
await this.table.add(batchRecords);
|
|
123
130
|
}
|
|
124
131
|
if (this.table && batch.length > 0) {
|
|
125
|
-
const deleteConditions = batch.map(
|
|
132
|
+
const deleteConditions = batch.map(
|
|
133
|
+
(f) => `(file_path = '${this.escapeString(f.path)}' AND file_hash != '${this.escapeString(f.hash)}')`
|
|
134
|
+
).join(" OR ");
|
|
126
135
|
await this.table.delete(deleteConditions);
|
|
127
136
|
}
|
|
128
137
|
}
|
|
@@ -165,7 +174,7 @@ var VectorStore = class {
|
|
|
165
174
|
}
|
|
166
175
|
/**
|
|
167
176
|
* 批量获取多个文件的 chunks(性能优化:单次查询替代 N 次循环)
|
|
168
|
-
*
|
|
177
|
+
*
|
|
169
178
|
* 适用于 GraphExpander 扩展、词法召回等需要批量获取的场景
|
|
170
179
|
* @returns Map<filePath, ChunkRecord[]>,每个文件的 chunks 已按 chunk_index 排序
|
|
171
180
|
*/
|
|
@@ -242,9 +251,6 @@ async function closeAllVectorStores() {
|
|
|
242
251
|
vectorStores.clear();
|
|
243
252
|
}
|
|
244
253
|
|
|
245
|
-
// src/indexer/index.ts
|
|
246
|
-
import "better-sqlite3";
|
|
247
|
-
|
|
248
254
|
// src/api/embedding.ts
|
|
249
255
|
var ProgressTracker = class {
|
|
250
256
|
completed = 0;
|
|
@@ -391,20 +397,17 @@ var RateLimitController = class {
|
|
|
391
397
|
},
|
|
392
398
|
"\u901F\u7387\u9650\u5236\uFF1A\u89E6\u53D1 429\uFF0C\u6682\u505C\u6240\u6709\u8BF7\u6C42"
|
|
393
399
|
);
|
|
394
|
-
let resumeResolve
|
|
400
|
+
let resumeResolve = () => {
|
|
401
|
+
};
|
|
395
402
|
this.pausePromise = new Promise((resolve) => {
|
|
396
403
|
resumeResolve = resolve;
|
|
397
404
|
});
|
|
398
405
|
await sleep(this.backoffMs);
|
|
399
406
|
this.backoffMs = Math.min(this.maxBackoffMs, this.backoffMs * 2);
|
|
400
407
|
this.isPaused = false;
|
|
401
|
-
const resolvedPromise = this.pausePromise;
|
|
402
408
|
this.pausePromise = null;
|
|
403
409
|
resumeResolve();
|
|
404
|
-
logger.info(
|
|
405
|
-
{ waitMs: this.backoffMs },
|
|
406
|
-
"\u901F\u7387\u9650\u5236\uFF1A\u6062\u590D\u8BF7\u6C42"
|
|
407
|
-
);
|
|
410
|
+
logger.info({ waitMs: this.backoffMs }, "\u901F\u7387\u9650\u5236\uFF1A\u6062\u590D\u8BF7\u6C42");
|
|
408
411
|
}
|
|
409
412
|
/**
|
|
410
413
|
* 获取当前状态(用于调试)
|
|
@@ -463,10 +466,13 @@ var EmbeddingClient = class {
|
|
|
463
466
|
return batchResults.flat();
|
|
464
467
|
}
|
|
465
468
|
/**
|
|
466
|
-
*
|
|
469
|
+
* 带速率限制和网络错误重试的批次处理
|
|
467
470
|
* 使用循环而非递归,避免栈溢出和槽位泄漏
|
|
468
471
|
*/
|
|
469
472
|
async processWithRateLimit(texts, startIndex, progress) {
|
|
473
|
+
const MAX_NETWORK_RETRIES = 3;
|
|
474
|
+
const INITIAL_RETRY_DELAY_MS = 1e3;
|
|
475
|
+
let networkRetries = 0;
|
|
470
476
|
while (true) {
|
|
471
477
|
await this.rateLimiter.acquire();
|
|
472
478
|
try {
|
|
@@ -474,17 +480,75 @@ var EmbeddingClient = class {
|
|
|
474
480
|
this.rateLimiter.releaseSuccess();
|
|
475
481
|
return result;
|
|
476
482
|
} catch (err) {
|
|
477
|
-
const
|
|
483
|
+
const error = err;
|
|
484
|
+
const errorMessage = error.message || "";
|
|
485
|
+
const isRateLimited = errorMessage.includes("429") || errorMessage.includes("rate");
|
|
486
|
+
const isNetworkError = this.isNetworkError(err);
|
|
478
487
|
if (isRateLimited) {
|
|
479
488
|
this.rateLimiter.releaseForRetry();
|
|
480
489
|
await this.rateLimiter.triggerRateLimit();
|
|
490
|
+
networkRetries = 0;
|
|
491
|
+
} else if (isNetworkError && networkRetries < MAX_NETWORK_RETRIES) {
|
|
492
|
+
networkRetries++;
|
|
493
|
+
const delayMs = INITIAL_RETRY_DELAY_MS * 2 ** (networkRetries - 1);
|
|
494
|
+
logger.warn(
|
|
495
|
+
{
|
|
496
|
+
error: errorMessage,
|
|
497
|
+
retry: networkRetries,
|
|
498
|
+
maxRetries: MAX_NETWORK_RETRIES,
|
|
499
|
+
delayMs
|
|
500
|
+
},
|
|
501
|
+
"\u7F51\u7EDC\u9519\u8BEF\uFF0C\u51C6\u5907\u91CD\u8BD5"
|
|
502
|
+
);
|
|
503
|
+
this.rateLimiter.releaseForRetry();
|
|
504
|
+
await sleep(delayMs);
|
|
481
505
|
} else {
|
|
482
506
|
this.rateLimiter.releaseFailure();
|
|
507
|
+
if (isNetworkError) {
|
|
508
|
+
logger.error({ error: errorMessage, retries: networkRetries }, "\u7F51\u7EDC\u9519\u8BEF\u91CD\u8BD5\u6B21\u6570\u8017\u5C3D");
|
|
509
|
+
}
|
|
483
510
|
throw err;
|
|
484
511
|
}
|
|
485
512
|
}
|
|
486
513
|
}
|
|
487
514
|
}
|
|
515
|
+
/**
|
|
516
|
+
* 判断是否为网络错误
|
|
517
|
+
*
|
|
518
|
+
* 常见网络错误类型:
|
|
519
|
+
* - terminated: 连接被中断(TLS 断开)
|
|
520
|
+
* - ECONNRESET: 连接被远端重置
|
|
521
|
+
* - ETIMEDOUT: 连接超时
|
|
522
|
+
* - ENOTFOUND: DNS 解析失败
|
|
523
|
+
* - fetch failed: 通用 fetch 失败
|
|
524
|
+
* - socket hang up: 套接字意外关闭
|
|
525
|
+
*/
|
|
526
|
+
isNetworkError(err) {
|
|
527
|
+
const error = err;
|
|
528
|
+
const message = (error.message || "").toLowerCase();
|
|
529
|
+
const code = error.code || "";
|
|
530
|
+
const networkErrorPatterns = [
|
|
531
|
+
"terminated",
|
|
532
|
+
"econnreset",
|
|
533
|
+
"etimedout",
|
|
534
|
+
"enotfound",
|
|
535
|
+
"econnrefused",
|
|
536
|
+
"fetch failed",
|
|
537
|
+
"socket hang up",
|
|
538
|
+
"network",
|
|
539
|
+
"aborted"
|
|
540
|
+
];
|
|
541
|
+
for (const pattern of networkErrorPatterns) {
|
|
542
|
+
if (message.includes(pattern)) {
|
|
543
|
+
return true;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
const networkErrorCodes = ["ECONNRESET", "ETIMEDOUT", "ENOTFOUND", "ECONNREFUSED", "EPIPE"];
|
|
547
|
+
if (networkErrorCodes.includes(code)) {
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
488
552
|
/**
|
|
489
553
|
* 处理单个批次(单次请求,不含重试逻辑)
|
|
490
554
|
*/
|
|
@@ -558,7 +622,7 @@ var Indexer = class {
|
|
|
558
622
|
}
|
|
559
623
|
/**
|
|
560
624
|
* 处理扫描结果,更新向量索引
|
|
561
|
-
*
|
|
625
|
+
*
|
|
562
626
|
* @param db SQLite 数据库实例
|
|
563
627
|
* @param results 文件处理结果
|
|
564
628
|
*/
|
|
@@ -611,14 +675,19 @@ var Indexer = class {
|
|
|
611
675
|
stats.errors = indexResult.errors;
|
|
612
676
|
}
|
|
613
677
|
logger.info(
|
|
614
|
-
{
|
|
678
|
+
{
|
|
679
|
+
indexed: stats.indexed,
|
|
680
|
+
deleted: stats.deleted,
|
|
681
|
+
errors: stats.errors,
|
|
682
|
+
skipped: stats.skipped
|
|
683
|
+
},
|
|
615
684
|
"\u5411\u91CF\u7D22\u5F15\u5B8C\u6210"
|
|
616
685
|
);
|
|
617
686
|
return stats;
|
|
618
687
|
}
|
|
619
688
|
/**
|
|
620
689
|
* 批量索引文件(性能优化版)
|
|
621
|
-
*
|
|
690
|
+
*
|
|
622
691
|
* 优化策略:
|
|
623
692
|
* 1. Embedding 已批量化(原有)
|
|
624
693
|
* 2. LanceDB 写入批量化:N 次 upsertFile → 1 次 batchUpsertFiles
|
|
@@ -649,8 +718,12 @@ var Indexer = class {
|
|
|
649
718
|
const results = await this.embeddingClient.embedBatch(allTexts);
|
|
650
719
|
embeddings = results.map((r) => r.embedding);
|
|
651
720
|
} catch (err) {
|
|
652
|
-
|
|
653
|
-
|
|
721
|
+
const error = err;
|
|
722
|
+
logger.error({ error: error.message, stack: error.stack }, "Embedding \u5931\u8D25");
|
|
723
|
+
clearVectorIndexHash(
|
|
724
|
+
db,
|
|
725
|
+
files.map((f) => f.path)
|
|
726
|
+
);
|
|
654
727
|
return { success: 0, errors: files.length };
|
|
655
728
|
}
|
|
656
729
|
const filesToUpsert = [];
|
|
@@ -690,23 +763,35 @@ var Indexer = class {
|
|
|
690
763
|
filePath: record.file_path,
|
|
691
764
|
chunkIndex: record.chunk_index,
|
|
692
765
|
breadcrumb: record.breadcrumb,
|
|
693
|
-
content: record.breadcrumb
|
|
766
|
+
content: `${record.breadcrumb}
|
|
767
|
+
${record.display_code}`
|
|
694
768
|
});
|
|
695
769
|
}
|
|
696
770
|
filesToUpsert.push({ path: file.path, hash: file.hash, records });
|
|
697
771
|
successFiles.push({ path: file.path, hash: file.hash });
|
|
698
772
|
} catch (err) {
|
|
699
|
-
|
|
773
|
+
const error = err;
|
|
774
|
+
logger.error(
|
|
775
|
+
{ path: file.path, error: error.message, stack: error.stack },
|
|
776
|
+
"\u7EC4\u88C5 ChunkRecord \u5931\u8D25"
|
|
777
|
+
);
|
|
700
778
|
errorFiles.push(file.path);
|
|
701
779
|
}
|
|
702
780
|
}
|
|
703
781
|
if (filesToUpsert.length > 0) {
|
|
704
782
|
try {
|
|
705
|
-
await this.vectorStore
|
|
706
|
-
logger.info(
|
|
783
|
+
await this.vectorStore?.batchUpsertFiles(filesToUpsert);
|
|
784
|
+
logger.info(
|
|
785
|
+
{ files: filesToUpsert.length, chunks: allFtsChunks.length },
|
|
786
|
+
"LanceDB \u6279\u91CF\u5199\u5165\u5B8C\u6210"
|
|
787
|
+
);
|
|
707
788
|
} catch (err) {
|
|
708
|
-
|
|
709
|
-
|
|
789
|
+
const error = err;
|
|
790
|
+
logger.error({ error: error.message, stack: error.stack }, "LanceDB \u6279\u91CF\u5199\u5165\u5931\u8D25");
|
|
791
|
+
clearVectorIndexHash(
|
|
792
|
+
db,
|
|
793
|
+
files.map((f) => f.path)
|
|
794
|
+
);
|
|
710
795
|
return { success: 0, errors: files.length };
|
|
711
796
|
}
|
|
712
797
|
}
|
|
@@ -715,18 +800,19 @@ var Indexer = class {
|
|
|
715
800
|
const pathsToDelete = filesToUpsert.map((f) => f.path);
|
|
716
801
|
batchDeleteFileChunksFts(db, pathsToDelete);
|
|
717
802
|
batchUpsertChunkFts(db, allFtsChunks);
|
|
718
|
-
logger.info(
|
|
803
|
+
logger.info(
|
|
804
|
+
{ files: pathsToDelete.length, chunks: allFtsChunks.length },
|
|
805
|
+
"FTS \u6279\u91CF\u66F4\u65B0\u5B8C\u6210"
|
|
806
|
+
);
|
|
719
807
|
} catch (err) {
|
|
720
|
-
|
|
808
|
+
const error = err;
|
|
809
|
+
logger.warn({ error: error.message }, "FTS \u6279\u91CF\u66F4\u65B0\u5931\u8D25\uFF08\u5411\u91CF\u7D22\u5F15\u5DF2\u6210\u529F\uFF09");
|
|
721
810
|
}
|
|
722
811
|
}
|
|
723
812
|
if (successFiles.length > 0) {
|
|
724
813
|
batchUpdateVectorIndexHash(db, successFiles);
|
|
725
814
|
}
|
|
726
|
-
logger.info(
|
|
727
|
-
{ success: successFiles.length, errors: errorFiles.length },
|
|
728
|
-
"\u6279\u91CF\u7D22\u5F15\u5B8C\u6210"
|
|
729
|
-
);
|
|
815
|
+
logger.info({ success: successFiles.length, errors: errorFiles.length }, "\u6279\u91CF\u7D22\u5F15\u5B8C\u6210");
|
|
730
816
|
return { success: successFiles.length, errors: errorFiles.length };
|
|
731
817
|
}
|
|
732
818
|
/**
|
|
@@ -747,7 +833,7 @@ var Indexer = class {
|
|
|
747
833
|
if (!this.vectorStore) {
|
|
748
834
|
await this.init();
|
|
749
835
|
}
|
|
750
|
-
return this.vectorStore
|
|
836
|
+
return this.vectorStore?.search(queryVector, limit, filter);
|
|
751
837
|
}
|
|
752
838
|
/**
|
|
753
839
|
* 文本搜索(先 embedding 再向量搜索)
|
|
@@ -763,7 +849,7 @@ var Indexer = class {
|
|
|
763
849
|
if (!this.vectorStore) {
|
|
764
850
|
await this.init();
|
|
765
851
|
}
|
|
766
|
-
await this.vectorStore
|
|
852
|
+
await this.vectorStore?.clear();
|
|
767
853
|
}
|
|
768
854
|
/**
|
|
769
855
|
* 获取索引统计
|
|
@@ -772,7 +858,7 @@ var Indexer = class {
|
|
|
772
858
|
if (!this.vectorStore) {
|
|
773
859
|
await this.init();
|
|
774
860
|
}
|
|
775
|
-
const count = await this.vectorStore
|
|
861
|
+
const count = await this.vectorStore?.count() ?? 0;
|
|
776
862
|
return { totalChunks: count };
|
|
777
863
|
}
|
|
778
864
|
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
codebaseRetrievalSchema,
|
|
3
3
|
handleCodebaseRetrieval
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-O3HDM3CF.js";
|
|
5
|
+
import "./chunk-VW5RACJC.js";
|
|
6
|
+
import "./chunk-C7XDGBT5.js";
|
|
7
|
+
import "./chunk-SKBAE26T.js";
|
|
7
8
|
export {
|
|
8
9
|
codebaseRetrievalSchema,
|
|
9
10
|
handleCodebaseRetrieval
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
-
DEFAULT_EXCLUDE_PATTERNS,
|
|
3
2
|
checkEmbeddingEnv,
|
|
4
3
|
checkRerankerEnv,
|
|
5
4
|
getEmbeddingConfig,
|
|
6
5
|
getExcludePatterns,
|
|
7
6
|
getRerankerConfig,
|
|
8
|
-
isDev
|
|
9
|
-
|
|
7
|
+
isDev,
|
|
8
|
+
isMcpMode
|
|
9
|
+
} from "./chunk-SKBAE26T.js";
|
|
10
10
|
export {
|
|
11
|
-
DEFAULT_EXCLUDE_PATTERNS,
|
|
12
11
|
checkEmbeddingEnv,
|
|
13
12
|
checkRerankerEnv,
|
|
14
13
|
getEmbeddingConfig,
|
|
15
14
|
getExcludePatterns,
|
|
16
15
|
getRerankerConfig,
|
|
17
|
-
isDev
|
|
16
|
+
isDev,
|
|
17
|
+
isMcpMode
|
|
18
18
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
scan
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-QWQ64TBE.js";
|
|
5
|
+
import "./chunk-WWYSLCNZ.js";
|
|
6
|
+
import {
|
|
7
|
+
generateProjectId
|
|
8
|
+
} from "./chunk-VW5RACJC.js";
|
|
6
9
|
import {
|
|
7
|
-
generateProjectId,
|
|
8
10
|
logger
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-C7XDGBT5.js";
|
|
12
|
+
import "./chunk-SKBAE26T.js";
|
|
11
13
|
|
|
12
14
|
// src/index.ts
|
|
13
|
-
import cac from "cac";
|
|
14
15
|
import { promises as fs } from "fs";
|
|
15
|
-
import path from "path";
|
|
16
16
|
import os from "os";
|
|
17
|
+
import path from "path";
|
|
18
|
+
import cac from "cac";
|
|
17
19
|
var cli = cac("contextweaver");
|
|
18
20
|
cli.command("init", "\u521D\u59CB\u5316 ContextWeaver \u914D\u7F6E").action(async () => {
|
|
19
21
|
const configDir = path.join(os.homedir(), ".contextweaver");
|
|
@@ -23,8 +25,9 @@ cli.command("init", "\u521D\u59CB\u5316 ContextWeaver \u914D\u7F6E").action(asyn
|
|
|
23
25
|
await fs.mkdir(configDir, { recursive: true });
|
|
24
26
|
logger.info(`\u521B\u5EFA\u914D\u7F6E\u76EE\u5F55: ${configDir}`);
|
|
25
27
|
} catch (err) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
const error = err;
|
|
29
|
+
if (error.code !== "EEXIST") {
|
|
30
|
+
logger.error({ err, stack: error.stack }, `\u521B\u5EFA\u914D\u7F6E\u76EE\u5F55\u5931\u8D25: ${error.message}`);
|
|
28
31
|
process.exit(1);
|
|
29
32
|
}
|
|
30
33
|
logger.info(`\u914D\u7F6E\u76EE\u5F55\u5DF2\u5B58\u5728: ${configDir}`);
|
|
@@ -58,7 +61,8 @@ RERANK_TOP_N=20
|
|
|
58
61
|
await fs.writeFile(envFile, defaultEnvContent);
|
|
59
62
|
logger.info(`\u521B\u5EFA .env \u6587\u4EF6: ${envFile}`);
|
|
60
63
|
} catch (err) {
|
|
61
|
-
|
|
64
|
+
const error = err;
|
|
65
|
+
logger.error({ err, stack: error.stack }, `\u521B\u5EFA .env \u6587\u4EF6\u5931\u8D25: ${error.message}`);
|
|
62
66
|
process.exit(1);
|
|
63
67
|
}
|
|
64
68
|
logger.info("\u4E0B\u4E00\u6B65\u64CD\u4F5C:");
|
|
@@ -80,7 +84,7 @@ cli.command("index [path]", "\u626B\u63CF\u4EE3\u7801\u5E93\u5E76\u5EFA\u7ACB\u7
|
|
|
80
84
|
force: options.force,
|
|
81
85
|
onProgress: (current, total) => {
|
|
82
86
|
const percent = (current / total * 100).toFixed(1);
|
|
83
|
-
|
|
87
|
+
logger.info({ current, total, percent: `${percent}%` }, "\u626B\u63CF\u8FDB\u5EA6");
|
|
84
88
|
}
|
|
85
89
|
});
|
|
86
90
|
process.stdout.write("\n");
|
|
@@ -95,36 +99,47 @@ cli.command("index [path]", "\u626B\u63CF\u4EE3\u7801\u5E93\u5E76\u5EFA\u7ACB\u7
|
|
|
95
99
|
logger.info(`\u8DF3\u8FC7: ${stats.skipped}`);
|
|
96
100
|
logger.info(`\u9519\u8BEF: ${stats.errors}`);
|
|
97
101
|
} catch (err) {
|
|
98
|
-
|
|
102
|
+
const error = err;
|
|
103
|
+
logger.error({ err, stack: error.stack }, `\u626B\u63CF\u5931\u8D25: ${error.message}`);
|
|
99
104
|
process.exit(1);
|
|
100
105
|
}
|
|
101
106
|
});
|
|
102
107
|
cli.command("mcp", "\u542F\u52A8 MCP \u670D\u52A1\u5668").action(async () => {
|
|
103
|
-
const { startMcpServer } = await import("./server-
|
|
108
|
+
const { startMcpServer } = await import("./server-7PYHHTOM.js");
|
|
104
109
|
try {
|
|
105
110
|
await startMcpServer();
|
|
106
|
-
} catch (
|
|
107
|
-
|
|
111
|
+
} catch (err) {
|
|
112
|
+
const error = err;
|
|
113
|
+
logger.error(
|
|
114
|
+
{ error: error.message, stack: error.stack },
|
|
115
|
+
`MCP \u670D\u52A1\u5668\u542F\u52A8\u5931\u8D25: ${error.message}`
|
|
116
|
+
);
|
|
108
117
|
process.exit(1);
|
|
109
118
|
}
|
|
110
119
|
});
|
|
111
|
-
cli.command("search", "\u672C\u5730\u68C0\u7D22\uFF08\u53C2\u6570\u5BF9\u9F50 MCP\uFF09").option("--repo-path <path>", "\u4EE3\u7801\u5E93\u6839\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09").option("--information-request <text>", "\u81EA\u7136\u8BED\u8A00\u95EE\u9898\u63CF\u8FF0\uFF08\u5FC5\u586B\uFF09").option("--technical-terms <terms>", "\u7CBE\u786E\u672F\u8BED\uFF08\u9017\u53F7\u5206\u9694\uFF09").option("--zen", "\u4F7F\u7528 MCP Zen \u914D\u7F6E\uFF08\u9ED8\u8BA4\u5F00\u542F\uFF09").action(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
120
|
+
cli.command("search", "\u672C\u5730\u68C0\u7D22\uFF08\u53C2\u6570\u5BF9\u9F50 MCP\uFF09").option("--repo-path <path>", "\u4EE3\u7801\u5E93\u6839\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09").option("--information-request <text>", "\u81EA\u7136\u8BED\u8A00\u95EE\u9898\u63CF\u8FF0\uFF08\u5FC5\u586B\uFF09").option("--technical-terms <terms>", "\u7CBE\u786E\u672F\u8BED\uFF08\u9017\u53F7\u5206\u9694\uFF09").option("--zen", "\u4F7F\u7528 MCP Zen \u914D\u7F6E\uFF08\u9ED8\u8BA4\u5F00\u542F\uFF09").action(
|
|
121
|
+
async (options) => {
|
|
122
|
+
const repoPath = options.repoPath ? path.resolve(options.repoPath) : process.cwd();
|
|
123
|
+
const informationRequest = options.informationRequest;
|
|
124
|
+
if (!informationRequest) {
|
|
125
|
+
logger.error("\u7F3A\u5C11 --information-request");
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
const technicalTerms = (options.technicalTerms || "").split(",").map((t) => t.trim()).filter(Boolean);
|
|
129
|
+
const useZen = options.zen !== false;
|
|
130
|
+
const { handleCodebaseRetrieval } = await import("./codebaseRetrieval-AV4GK6FT.js");
|
|
131
|
+
const response = await handleCodebaseRetrieval(
|
|
132
|
+
{
|
|
133
|
+
repo_path: repoPath,
|
|
134
|
+
information_request: informationRequest,
|
|
135
|
+
technical_terms: technicalTerms.length > 0 ? technicalTerms : void 0
|
|
136
|
+
},
|
|
137
|
+
useZen ? void 0 : {}
|
|
138
|
+
);
|
|
139
|
+
const text = response.content.map((item) => item.text).join("\n");
|
|
140
|
+
process.stdout.write(`${text}
|
|
141
|
+
`);
|
|
117
142
|
}
|
|
118
|
-
|
|
119
|
-
const useZen = options.zen !== false;
|
|
120
|
-
const { handleCodebaseRetrieval } = await import("./codebaseRetrieval-GPSIAFSZ.js");
|
|
121
|
-
const response = await handleCodebaseRetrieval({
|
|
122
|
-
repo_path: repoPath,
|
|
123
|
-
information_request: informationRequest,
|
|
124
|
-
technical_terms: technicalTerms.length > 0 ? technicalTerms : void 0
|
|
125
|
-
}, useZen ? void 0 : {});
|
|
126
|
-
const text = response.content.map((item) => item.text).join("\n");
|
|
127
|
-
process.stdout.write(text + "\n");
|
|
128
|
-
});
|
|
143
|
+
);
|
|
129
144
|
cli.help();
|
|
130
145
|
cli.parse();
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-C7XDGBT5.js";
|
|
4
|
+
import "./chunk-SKBAE26T.js";
|
|
5
|
+
|
|
6
|
+
// src/utils/lock.ts
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import os from "os";
|
|
9
|
+
import path from "path";
|
|
10
|
+
var BASE_DIR = path.join(os.homedir(), ".contextweaver");
|
|
11
|
+
var LOCK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
12
|
+
var LOCK_CHECK_INTERVAL_MS = 100;
|
|
13
|
+
function getLockFilePath(projectId) {
|
|
14
|
+
return path.join(BASE_DIR, projectId, "index.lock");
|
|
15
|
+
}
|
|
16
|
+
function isLockValid(lockPath) {
|
|
17
|
+
try {
|
|
18
|
+
if (!fs.existsSync(lockPath)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const content = fs.readFileSync(lockPath, "utf-8");
|
|
22
|
+
const lockInfo = JSON.parse(content);
|
|
23
|
+
if (Date.now() - lockInfo.timestamp > LOCK_TIMEOUT_MS) {
|
|
24
|
+
logger.warn({ lockInfo, lockPath }, "\u9501\u5DF2\u8D85\u65F6");
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
process.kill(lockInfo.pid, 0);
|
|
29
|
+
return true;
|
|
30
|
+
} catch {
|
|
31
|
+
logger.warn({ pid: lockInfo.pid }, "\u6301\u6709\u9501\u7684\u8FDB\u7A0B\u5DF2\u6B7B\u4EA1");
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
} catch (err) {
|
|
35
|
+
const error = err;
|
|
36
|
+
logger.debug({ error: error.message }, "\u8BFB\u53D6\u9501\u6587\u4EF6\u5931\u8D25");
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function acquireLock(projectId, operation, timeoutMs = 3e4) {
|
|
41
|
+
const lockPath = getLockFilePath(projectId);
|
|
42
|
+
const dir = path.dirname(lockPath);
|
|
43
|
+
if (!fs.existsSync(dir)) {
|
|
44
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
const startTime = Date.now();
|
|
47
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
48
|
+
if (!isLockValid(lockPath)) {
|
|
49
|
+
try {
|
|
50
|
+
const lockInfo = {
|
|
51
|
+
pid: process.pid,
|
|
52
|
+
timestamp: Date.now(),
|
|
53
|
+
operation
|
|
54
|
+
};
|
|
55
|
+
fs.writeFileSync(lockPath, JSON.stringify(lockInfo), { flag: "w" });
|
|
56
|
+
const verifyContent = fs.readFileSync(lockPath, "utf-8");
|
|
57
|
+
const verifyInfo = JSON.parse(verifyContent);
|
|
58
|
+
if (verifyInfo.pid === process.pid) {
|
|
59
|
+
logger.debug({ projectId: projectId.slice(0, 10), operation }, "\u83B7\u53D6\u9501\u6210\u529F");
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
} catch (err) {
|
|
63
|
+
const error = err;
|
|
64
|
+
logger.debug({ error: error.message }, "\u83B7\u53D6\u9501\u5931\u8D25\uFF0C\u91CD\u8BD5\u4E2D...");
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
logger.debug({ projectId: projectId.slice(0, 10) }, "\u7B49\u5F85\u9501\u91CA\u653E...");
|
|
68
|
+
}
|
|
69
|
+
await new Promise((resolve) => setTimeout(resolve, LOCK_CHECK_INTERVAL_MS));
|
|
70
|
+
}
|
|
71
|
+
logger.warn({ projectId: projectId.slice(0, 10), timeoutMs }, "\u83B7\u53D6\u9501\u8D85\u65F6");
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
function releaseLock(projectId) {
|
|
75
|
+
const lockPath = getLockFilePath(projectId);
|
|
76
|
+
try {
|
|
77
|
+
if (!fs.existsSync(lockPath)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const content = fs.readFileSync(lockPath, "utf-8");
|
|
81
|
+
const lockInfo = JSON.parse(content);
|
|
82
|
+
if (lockInfo.pid === process.pid) {
|
|
83
|
+
fs.unlinkSync(lockPath);
|
|
84
|
+
logger.debug({ projectId: projectId.slice(0, 10) }, "\u91CA\u653E\u9501\u6210\u529F");
|
|
85
|
+
} else {
|
|
86
|
+
logger.warn({ ownPid: process.pid, lockPid: lockInfo.pid }, "\u5C1D\u8BD5\u91CA\u653E\u975E\u81EA\u5DF1\u6301\u6709\u7684\u9501");
|
|
87
|
+
}
|
|
88
|
+
} catch (err) {
|
|
89
|
+
const error = err;
|
|
90
|
+
logger.debug({ error: error.message }, "\u91CA\u653E\u9501\u65F6\u51FA\u9519");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function withLock(projectId, operation, fn, timeoutMs = 3e4) {
|
|
94
|
+
const acquired = await acquireLock(projectId, operation, timeoutMs);
|
|
95
|
+
if (!acquired) {
|
|
96
|
+
throw new Error(`\u65E0\u6CD5\u83B7\u53D6\u9879\u76EE\u9501 (${projectId.slice(0, 10)})\uFF0C\u5176\u4ED6\u8FDB\u7A0B\u6B63\u5728\u64CD\u4F5C\u7D22\u5F15`);
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
return await fn();
|
|
100
|
+
} finally {
|
|
101
|
+
releaseLock(projectId);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export {
|
|
105
|
+
withLock
|
|
106
|
+
};
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
codebaseRetrievalSchema,
|
|
3
3
|
handleCodebaseRetrieval
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-O3HDM3CF.js";
|
|
5
|
+
import "./chunk-VW5RACJC.js";
|
|
5
6
|
import {
|
|
6
7
|
logger
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-C7XDGBT5.js";
|
|
9
|
+
import "./chunk-SKBAE26T.js";
|
|
9
10
|
|
|
10
11
|
// src/mcp/server.ts
|
|
11
12
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
12
13
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13
|
-
import {
|
|
14
|
-
CallToolRequestSchema,
|
|
15
|
-
ListToolsRequestSchema
|
|
16
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
14
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
17
15
|
var SERVER_NAME = "contextweaver";
|
|
18
16
|
var TOOLS = [
|
|
19
17
|
{
|
|
@@ -108,7 +106,8 @@ async function startMcpServer() {
|
|
|
108
106
|
default:
|
|
109
107
|
throw new Error(`Unknown tool: ${name}`);
|
|
110
108
|
}
|
|
111
|
-
} catch (
|
|
109
|
+
} catch (err) {
|
|
110
|
+
const error = err;
|
|
112
111
|
logger.error({ error: error.message, stack: error.stack, tool: name }, "\u5DE5\u5177\u8C03\u7528\u5931\u8D25");
|
|
113
112
|
return {
|
|
114
113
|
content: [
|